Compare commits

...

147 Commits

Author SHA1 Message Date
cedoor
c1842eefc3 chore: v3.2.3
Former-commit-id: 59d2d1367c
2023-03-13 22:52:08 +00:00
Cedoor
320187ff89 Merge pull request #276 from semaphore-protocol/fix/bundle-poseidon
JS bundles with Poseidon functions

Former-commit-id: e135b1d8fa
2023-03-13 22:51:20 +00:00
cedoor
abbf1a1d30 chore: update lockfile
Former-commit-id: 982ee86208
2023-03-13 22:41:37 +00:00
cedoor
59269b067d build: bundle poseidon-lite functions with rollup
Former-commit-id: 797e62dd28
2023-03-13 22:32:23 +00:00
cedoor
21bd9fe540 ci: update auto-assign workflow name
Former-commit-id: 6a3079bf52
2023-03-13 19:35:41 +00:00
Cedoor
f60a4c02f1 Merge pull request #274 from semaphore-protocol/chore/husky
New Husky script to check commit messages' format

Former-commit-id: e00dc8eca8
2023-03-13 19:30:46 +00:00
cedoor
d012310ae1 chore: add husky script to check commit message format
re #263


Former-commit-id: 77552b0da3
2023-03-13 19:26:40 +00:00
Cedoor
f01cb91472 chore: update github token
Former-commit-id: d33d306478
2023-03-13 19:25:17 +00:00
cedoor
31e6954aff style: format code with prettier
Former-commit-id: 0137d6e759
2023-03-13 18:36:01 +00:00
Cedoor
7518d938d1 Merge pull request #273 from semaphore-protocol/chore/auto-assign-action
New Github workflow to assign PRs

Former-commit-id: f777603fba
2023-03-13 18:31:43 +00:00
Cedoor
083d8a03a4 chore: update team name
Former-commit-id: 07244fc6bc
2023-03-13 18:30:56 +00:00
Cedoor
16c9e90ae4 chore: set org devs as reviewers
Former-commit-id: b9db0663ec
2023-03-13 18:18:50 +00:00
Cedoor
1fe2745594 chore: add github workflow to auto assign PRs
Former-commit-id: 0c6df193e4
2023-03-13 16:57:52 +00:00
cedoor
18d4b740ca chore: v3.2.2
Former-commit-id: 5ea3b7e6ef
2023-03-13 11:52:02 +00:00
Cedoor
8392173370 Merge pull request #272 from semaphore-protocol/fix/data-ethers
Correct check for group existence

Former-commit-id: 46c2ad2c7a
2023-03-13 11:50:59 +00:00
cedoor
26490304e4 fix(data): set correct check for group existence
Former-commit-id: 7b938935ca
2023-03-13 11:40:23 +00:00
cedoor
1878ce2320 chore: v3.2.1
Former-commit-id: 5cc6877d15
2023-03-11 21:41:08 +00:00
cedoor
98aed04bd9 chore(contracts): include Semaphore.sol contract in npm files
Former-commit-id: d227ee1907
2023-03-11 21:40:38 +00:00
cedoor
5a0585cec2 chore: add refactor type to changelog
Former-commit-id: 4f7af880a9
2023-03-10 13:09:10 +00:00
cedoor
1a51263e5c chore: v3.2.0
Former-commit-id: 33d814fe6d
2023-03-10 11:19:52 +00:00
cedoor
4d7a976f06 chore(cli): remove unused ts paths
Former-commit-id: 915b95dfc6
2023-03-10 11:10:25 +00:00
cedoor
fe07103ff2 chore(cli-template-hardhat): add supported networks to hardhat
Former-commit-id: 0780751954
2023-03-10 11:03:17 +00:00
Cedoor
aa230345fb Merge pull request #270 from semaphore-protocol/chore/optimism-goerli
Semaphore support for Optimism Goerli network

Former-commit-id: 6344cf4954
2023-03-10 10:55:47 +00:00
Cedoor
d5c24c0f9c Merge pull request #269 from semaphore-protocol/chore/mumbai
Semaphore support for Mumbai network

Former-commit-id: e002b24f2a
2023-03-10 10:55:19 +00:00
Cedoor
c1752dcb2e Merge pull request #271 from vimwitch/poseidon-lite-upgrade
Poseidon lite upgrade

Former-commit-id: 21f2287570
2023-03-09 20:35:49 +00:00
Chance
b00ff99e5e chore: bump poseidon-lite version
Former-commit-id: 1a27d0b28b
2023-03-09 14:05:48 -06:00
cedoor
00e3c4a7f5 feat(data): add support for optimism goerli network
re #234


Former-commit-id: 3722b154bf
2023-03-09 18:21:44 +00:00
cedoor
0176a67e3a style(contracts): format code with prettier
Former-commit-id: d9f888ab70
2023-03-09 18:03:09 +00:00
cedoor
4fa9f41483 chore(contracts): deploy semaphore on optimism goerli network
re #234


Former-commit-id: 55a48a27d1
2023-03-09 18:02:16 +00:00
cedoor
64b9ef28e8 chore(contracts): update hardhat etherscan plugin
Former-commit-id: 4241be137a
2023-03-09 18:01:03 +00:00
cedoor
d7ce540f88 style(contracts): format code with prettier
Former-commit-id: 2e5e00ae55
2023-03-09 15:33:26 +00:00
cedoor
3411f55403 feat(data): add support for mumbai network
re #233


Former-commit-id: 6b498912df
2023-03-09 15:19:34 +00:00
cedoor
1bc492c39d chore(contracts): deploy semaphore on mumbai network
re #233


Former-commit-id: c98889632e
2023-03-09 14:28:35 +00:00
Cedoor
6197a03734 Merge pull request #268 from semaphore-protocol/chore/sepolia
Semaphore on Sepolia network

Former-commit-id: eceec87727
2023-03-09 11:40:03 +00:00
cedoor
6369fdfa71 style(contracts): format code with prettier
Former-commit-id: 1013516ba2
2023-03-09 11:39:51 +00:00
cedoor
48f779cd90 chore(contracts): deploy semaphore on sepolia network
re #183


Former-commit-id: a296541c6d
2023-03-09 11:28:17 +00:00
Cedoor
3ff6709a7b Merge pull request #267 from 0xdeenz/main
refactor(contracts): change Semaphore__InvalidProof to InvalidProof

Former-commit-id: 4fab94e058
2023-03-09 10:25:38 +00:00
0xdeenz
90bb4ccfa0 refactor(contracts): change Semaphore__InvalidProof to InvalidProof
Makes Pairing.sol errors more generic for greater reusability

fix #266


Former-commit-id: dc39c4ca44
2023-03-08 23:27:08 +01:00
Cedoor
5f29690641 Merge pull request #264 from semaphore-protocol/feat/data-package
New `@semaphore-protocol/data` package

Former-commit-id: 86e186cdc0
2023-03-08 21:30:33 +00:00
cedoor
2665a440fa build: add topological option to yarn workspaces cmd
Former-commit-id: b09e27477f
2023-03-08 21:18:38 +00:00
Cedoor
cf31913d27 docs: fix md syntax
Former-commit-id: 2b732f335c
2023-03-08 21:15:12 +00:00
cedoor
103a676455 docs(data): add await to usage functions
Former-commit-id: cce29363f9
2023-03-08 21:12:19 +00:00
cedoor
9aa5bc5840 fix(cli): set correct data class name
Former-commit-id: 180977841d
2023-03-08 21:11:49 +00:00
cedoor
894bc87952 docs(data): update readme file
re #256


Former-commit-id: a798acbe80
2023-03-08 20:57:32 +00:00
cedoor
cd4bd6ad91 chore(cli): sen new data package
Former-commit-id: bc165cd6e4
2023-03-08 20:32:02 +00:00
cedoor
dca7b67677 fix(data): add request header
Former-commit-id: 56e652a507
2023-03-08 20:31:40 +00:00
cedoor
60d698f6ee test(data): mock ethers tests
re #256


Former-commit-id: 9942b92f13
2023-03-07 18:24:25 +00:00
cedoor
f05e654f95 build(data): update rollup configuration
re #256


Former-commit-id: 9d27c3ac54
2023-03-06 22:14:28 +00:00
cedoor
9a4fe6ba64 chore: remove types folder from prettier ignore file
Former-commit-id: 925cc64d73
2023-03-06 17:44:44 +00:00
cedoor
c8d472a286 feat(data): create new data package
This new package allows devs to fetch on-chain data by using a Semaphore subgraph or the Ethers
library.

BREAKING CHANGE: The code of the old `@semaphore-protocol/subgraph` package has been moved to the
`@semaphore-protocol/data` package.

re #256


Former-commit-id: fc2f648acc
2023-03-06 17:43:42 +00:00
Cedoor
c9711b9007 Merge pull request #262 from vimwitch/identity-entropy
feat(identity): use sha512 to extract more entropy from message

Former-commit-id: 95e4a4e672
2023-03-05 21:07:39 +00:00
cedoor
8afcac50c2 chore(subgraph): remove subgraph package
re #256


Former-commit-id: b040c421ea
2023-03-05 20:12:46 +00:00
Chance
75c1c1373c chore: run prettier
Former-commit-id: d5d01142b1
2023-03-04 15:12:31 -06:00
Chance
2318628ca2 feat(identity): use sha512 to extract more entropy from message
Former-commit-id: ed63519423
2023-03-04 15:02:40 -06:00
Cedoor
2603600716 Merge pull request #261 from vimwitch/json-prepend-0x
feat(identity): prepend hex strings with 0x

Former-commit-id: 5a99792b68
2023-03-04 21:00:45 +00:00
Chance
3f0f1e006c feat(identity): prepend hex strings with 0x
Former-commit-id: 3c359e9a57
2023-03-04 14:47:03 -06:00
cedoor
2a4f2455d7 refactor(hardhat): update readme and set correct task name
Former-commit-id: 25e2442090
2023-03-01 19:35:57 +00:00
cedoor
0ea221d9ea Merge pull request #257 from shiyingt/feat/deploy-verifier-hardhat
add hardhat task to deploy SemaphoreVerifier.sol

Former-commit-id: 76146ed728
2023-03-01 20:20:59 +01:00
shiyingt
437674f3bc add hardhat task to deploy verifier
Former-commit-id: 934e4b2c06
2023-02-27 23:43:33 +08:00
shiyingt
4202186c0b revert all changes
Former-commit-id: b51f1d7820
2023-02-27 23:29:28 +08:00
cedoor
383e818833 docs(hardhat): update readme description
Former-commit-id: 865421a001
2023-02-23 11:54:04 +01:00
shiyingt
74014a42b5 run prettier write and eslint
Former-commit-id: 2ddbc51679
2023-02-22 22:39:10 +08:00
cedoor
e875fa4aa3 chore: update discord links
Former-commit-id: bd06422ef5
2023-02-22 13:22:46 +01:00
shiyingt
186dae79d2 fix hardhat task
Former-commit-id: 6987686426
2023-02-21 23:08:58 +08:00
shiyingt
195d545a95 fix hardhat task
Former-commit-id: 3105a0c095
2023-02-21 23:02:51 +08:00
cedoor
84245b9b1f Merge pull request #255 from semaphore-protocol/chore/bump-version
New NPM scripts to bump versions and publish NPM packages

Former-commit-id: 1bb2982f7d
2023-02-20 16:42:47 +01:00
cedoor
461e91f53b style: format code with prettier
Former-commit-id: 5c402f3ba2
2023-02-20 14:40:10 +01:00
cedoor
2893feca3c chore: add new scripts to bump versions & publish npm pkgs
re #254


Former-commit-id: 257080e478
2023-02-20 14:31:42 +01:00
cedoor
4be2973761 chore: update lockfile
re #254


Former-commit-id: 0b1519ea57
2023-02-20 12:46:15 +01:00
cedoor
01a30fd8af chore: add new workspace package
re #254


Former-commit-id: 9ef675f120
2023-02-20 12:44:36 +01:00
cedoor
a70c1b001c chore(yarn): add yarn plugin for versioning
re #254


Former-commit-id: a5df125378
2023-02-20 12:07:38 +01:00
shiyingt
30ed65d569 add hardhat task to deploy semaphore verifier only
Former-commit-id: 972790d158
2023-02-19 09:48:19 +08:00
cedoor
04a6649f2f Merge pull request #253 from semaphore-protocol/chore/release
Yarn patch to change `changelogithub` output

Former-commit-id: d2b959718c
2023-02-16 19:13:35 +01:00
cedoor
a337021d8d chore(release): create yarn patch to update changelogithub format
re #252


Former-commit-id: 739af09a5d
2023-02-16 18:41:15 +01:00
cedoor
4cd3863d03 docs(subgraph): update readme file
Former-commit-id: 6e036c0887
2023-02-15 22:00:03 +01:00
cedoor
c88639659d chore: update lockfile
Former-commit-id: 4f49736591
2023-02-15 20:57:45 +01:00
cedoor
0ca17d078b chore(semaphore): v3.1.0
Former-commit-id: 243c505d87
2023-02-15 20:47:23 +01:00
cedoor
f171506891 ci(release): add new workflow for automatic releases
Former-commit-id: a4fcb36fe0
2023-02-15 20:40:50 +01:00
cedoor
e085ecd0df test(subgraph): ignore some lines
Former-commit-id: 381c8668e4
2023-02-15 20:37:09 +01:00
cedoor
b744ee1e7b Merge pull request #250 from semaphore-protocol/refactor/cli-inquirer
Add inquirer in get-groups and get-group commands

Former-commit-id: 04ac6c2649
2023-02-15 20:18:15 +01:00
vplasencia
8d51a6689d refactor(cli): change some inquirer messages
Former-commit-id: 48ec4ba361
2023-02-15 12:29:32 -05:00
cedoor
7bec0a4e42 Merge pull request #251 from semaphore-protocol/feat/subgraph-improvements
Subgraph library improvements

Former-commit-id: 1d6f59229b
2023-02-15 18:16:11 +01:00
Saleel
71ed4d605d chore: add test for querying subgraph with filters
Former-commit-id: 11f061e586
2023-02-15 22:24:56 +05:30
Saleel
810a5358c0 chore: add tests for initalizing subgraph library with URL
Former-commit-id: 667929b09d
2023-02-15 22:05:56 +05:30
Saleel
636a9156b4 feat: add option to specify subgraph URL in subgraph library
Former-commit-id: fc4db2c8a1
2023-02-15 21:43:53 +05:30
Saleel
a679d047fa feat: add query filters to subgraph library
Former-commit-id: bc8f3495b6
2023-02-15 21:39:11 +05:30
vplasencia
6d3e685284 refactor(cli): add inquirer in get-group command
Former-commit-id: 68702bb5e4
2023-02-14 20:45:29 -05:00
vplasencia
3bfcf67b9d refactor(cli): add inquirer in get-groups command
Former-commit-id: f7fb159562
2023-02-14 16:41:00 -05:00
vplasencia
e16dbe663e refactor(cli): change project name message
Former-commit-id: 1aaaae0635
2023-02-14 16:30:14 -05:00
cedoor
0aca50ea34 Merge pull request #248 from semaphore-protocol/refactor/cli
Add node conventions and display an error for excess arguments in commands

Former-commit-id: 5604eff98d
2023-02-08 17:07:31 +01:00
vplasencia
54a6abdd2b refactor(cli): add node conventions and display an error for excess arguments in commands
Former-commit-id: 878ff0524e
2023-02-08 10:58:22 -05:00
cedoor
de32db6d2d Merge pull request #247 from semaphore-protocol/feature/cli-integrate-inquirer
Integrate Inquirer.js in create command in the CLI

Former-commit-id: 519b518c21
2023-02-08 16:32:46 +01:00
vplasencia
d43f9278e0 refactor(cli): remove projectDir variable
Former-commit-id: f7c1ca6e7e
2023-02-08 10:24:48 -05:00
vplasencia
f3d8c4ee75 feat(cli): integrate inquirer in create command
Former-commit-id: 7f63cc567e
2023-02-08 00:16:57 -05:00
cedoor
60fedc4e90 Merge pull request #244 from semaphore-protocol/feature/cli-check-latest-version
Check for latest version

Former-commit-id: dc969e0f55
2023-02-06 20:17:46 +01:00
cedoor
34d5743fa4 refactor(cli): move code to function to check latest version
re #188


Former-commit-id: fb0c016447
2023-02-06 20:06:16 +01:00
vplasencia
6810fcb5bb feat(cli): add check for latest version
Former-commit-id: ed81202bbd
2023-02-06 10:09:18 -05:00
cedoor
0bdf064614 Merge pull request #241 from semaphore-protocol/feature/group-id
Group class `id` attribute

Former-commit-id: cd3fa0c82c
2023-02-06 14:27:14 +01:00
cedoor
7c4d2c2007 feat(group): add id attribute to group class
re #240


Former-commit-id: 202fcc399c
2023-02-06 12:23:51 +01:00
cedoor
1bb57ca607 chore: update lockfile
Former-commit-id: b45c4f3510
2023-01-26 22:59:08 +01:00
cedoor
44860d70b9 chore: v3.0.0
Former-commit-id: 439a357173
2023-01-26 22:52:52 +01:00
cedoor
d286a8f1e8 Merge pull request #213 from semaphore-protocol/dev
Semaphore v3

Former-commit-id: 39a40f5f3b
2023-01-26 22:49:35 +01:00
cedoor
729ba41afd Merge pull request #236 from semaphore-protocol/vplasencia-patch-1
Change a comment

Former-commit-id: d27513d3f0
2023-01-26 11:47:04 +01:00
Vivian Plasencia
972fde32a7 Update group.ts
Former-commit-id: 5eb168c2ef
2023-01-25 21:55:17 -05:00
cedoor
c3d9e23e2a docs(contracts): update readme file
Former-commit-id: 9d12d3b050
2023-01-24 11:26:58 +01:00
cedoor
5fd7435c6d Merge pull request #232 from semaphore-protocol/refactor/proof
Proof generation/verification refactoring

Former-commit-id: f272aa26e9
2023-01-24 11:07:26 +01:00
cedoor
07888687a0 chore: update lockfile
Former-commit-id: 6b24210be1
2023-01-24 11:00:42 +01:00
cedoor
54c3a54597 chore: v3.0.0-beta.8
Former-commit-id: 8c952e9558
2023-01-24 10:38:47 +01:00
cedoor
81dba510c3 refactor(cli): update cli template tests
re #230


Former-commit-id: 5c027eef70
2023-01-24 10:37:32 +01:00
cedoor
343cdabe9c refactor(contracts): update contract tests
re #230


Former-commit-id: 1b654977b0
2023-01-23 20:29:08 +01:00
cedoor
fe6fbc1a0d refactor(proof): pack snarkjs proof
re #230


Former-commit-id: 8d68c09068
2023-01-23 20:27:56 +01:00
cedoor
f854492709 Merge pull request #231 from semaphore-protocol/feat/arbitrum-subgraph
Arbitrum subgraph

Former-commit-id: 4139c3e146
2023-01-23 17:08:48 +01:00
cedoor
30af236547 feat(subgraph): add arbitrum subgraph
re #229


Former-commit-id: 22fb9ebb87
2023-01-23 16:22:59 +01:00
cedoor
d65b4aae47 style: format code with prettier
Former-commit-id: 45d5815d95
2023-01-23 15:45:03 +01:00
cedoor
245fb2cba8 chore(contracts): deploy contracts on arbitrum
Former-commit-id: ea86ae0a3d
2023-01-23 15:41:15 +01:00
cedoor
f469ccc36a Merge pull request #228 from semaphore-protocol/refactor/bytes32-greeting
`bytes32` greeting

Former-commit-id: 5b2944105c
2023-01-19 12:12:12 +01:00
cedoor
8ee1bd9c1b Merge pull request #226 from semaphore-protocol/chore/fixed-dependencies
No version range for `snarkjs` and `poseidon-lite` dependencies

Former-commit-id: 5c03b72d37
2023-01-19 12:10:03 +01:00
cedoor
348a4327d7 refactor(cli): update greeting type to bytes32
Former-commit-id: c0ec1b5ecf
2023-01-18 20:06:48 +01:00
cedoor
faffde79f0 chore: update lockfile
re #225


Former-commit-id: 95e4a743b2
2023-01-17 15:29:05 +01:00
cedoor
89a83c7b6b chore: make snarkjs and poseidon-lite versions fixed
re #225


Former-commit-id: 59f1f624f2
2023-01-17 15:13:41 +01:00
cedoor
f36e8c1536 Merge pull request #223 from semaphore-protocol/fix/add-ts-suppress-error-comment
Fix/add ts suppress error comment

Former-commit-id: bdfc8d68a2
2023-01-11 18:34:32 +01:00
vplasencia
46ed508de8 Add TypeScript suppress error comment
Former-commit-id: 07728848f2
2023-01-11 12:12:48 -05:00
cedoor
821cdb7fd3 chore: update lockfile
Former-commit-id: 3bcec50e19
2023-01-09 17:18:17 +01:00
cedoor
dd2df7ab06 chore: v3.0.0-beta.7
Former-commit-id: ab1115c77a
2023-01-09 17:14:50 +01:00
cedoor
71fdabe65c Merge pull request #220 from semaphore-protocol/refactor/cli-init
Semaphore `create` command

Former-commit-id: a411b041b8
2023-01-09 17:03:22 +01:00
cedoor
cce6c31461 refactor(cli): replace init command with create
Former-commit-id: 72859d9b98
2023-01-09 11:05:54 +01:00
aguzmant103
f1aced7ee3 docs: adding GitPOAP link
Former-commit-id: f7e87c4442
2023-01-05 20:06:10 -06:00
cedoor
bd8b06bb7b docs(hardhat): update readme usage section
Former-commit-id: 20fa45c8f3
2023-01-05 17:44:46 +01:00
cedoor
9f692e57e8 chore: v3.0.0-beta.6
Former-commit-id: 1df18f81f7
2023-01-04 19:16:42 +01:00
cedoor
32aabf81d2 fix(subgraph): set correct goerli subgraph url
Former-commit-id: ce5c30b816
2023-01-04 19:08:52 +01:00
cedoor
a72a57ef63 style(subgraph): format code with prettier
Former-commit-id: 469e2a1310
2023-01-04 19:08:32 +01:00
cedoor
4e5b4495a3 chore: update lockfile
Former-commit-id: 1d4e7c509e
2023-01-04 18:56:23 +01:00
cedoor
578d426d1a chore(cli): v3.0.0-beta.5
Former-commit-id: 9a2db4b3bf
2023-01-04 18:52:16 +01:00
cedoor
362b025fac chore(hardhat): v3.0.0-beta.5
Former-commit-id: 23ec6dc55c
2023-01-04 18:48:54 +01:00
cedoor
84aa8d0eac chore(subgraph): v3.0.0-beta.5
Former-commit-id: ca1485bea8
2023-01-04 18:12:06 +01:00
cedoor
6592a62f25 chore(proof): v3.0.0-beta.5
Former-commit-id: e097a86875
2023-01-04 18:09:22 +01:00
cedoor
a59a2af50b chore(contracts): v3.0.0-beta.5
Former-commit-id: 268ef49ec5
2023-01-04 18:09:03 +01:00
cedoor
1e68ed1fec chore(group): v3.0.0-beta.5
Former-commit-id: 3ca1f59472
2023-01-04 18:08:14 +01:00
cedoor
2abd068cf8 chore(identity): v3.0.0-beta.5
Former-commit-id: 7f9978eb4f
2023-01-04 18:07:49 +01:00
cedoor
8b746ff2bc style(contracts): format code with prettier
Former-commit-id: ef9f927cbd
2023-01-04 13:27:15 +01:00
cedoor
23d9786e16 chore(contracts): deploy contracts on goerli
Former-commit-id: 2f14cc9fa9
2023-01-04 13:21:57 +01:00
cedoor
3b20402476 refactor(contracts): update function to verify contracts
Former-commit-id: cd65440f2c
2023-01-04 13:20:54 +01:00
cedoor
91bcc66c17 chore(cli): add arbitrum network to cli hardhat template
Former-commit-id: 3cdf7daa0f
2023-01-04 12:42:11 +01:00
cedoor
bcf9dad496 docs: update readme description
Former-commit-id: 64e47907b9
2023-01-04 11:03:33 +01:00
cedoor
29195519fb chore(cli): v0.5.0
Former-commit-id: bd30127759
2023-01-03 20:37:44 +01:00
cedoor
17efdb38af refactor(cli): add more info to init command
Former-commit-id: 3d559a6491
2023-01-03 20:36:11 +01:00
cedoor
8713e2b339 chore(cli): add files to be included in the package
re #216


Former-commit-id: 51a9d17520
2023-01-03 20:35:16 +01:00
92 changed files with 2348 additions and 18226 deletions

14
.github/workflows/auto-assign.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: auto-assign
on:
pull_request:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: wow-actions/auto-assign@v3
with:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
reviewers: org/core-devs

43
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: release
permissions:
contents: write
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- run: yarn version:release
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

4
.husky/commit-msg Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit $1

View File

@@ -16,9 +16,6 @@ packages/contracts/deployed-contracts/undefined.json
packages/contracts/deployed-contracts/hardhat.json
packages/contracts/deployed-contracts/localhost.json
# types
types
# circuits
circuits

View File

@@ -0,0 +1,23 @@
diff --git a/dist/shared/changelogithub.821fab93.mjs b/dist/shared/changelogithub.821fab93.mjs
index 5fc2100867613c20f7827eac8715a5fc28bdc39e..97bd8dff878b81c63d2220e496904f6f3933589a 100644
--- a/dist/shared/changelogithub.821fab93.mjs
+++ b/dist/shared/changelogithub.821fab93.mjs
@@ -181,7 +181,7 @@ function formatLine(commit, options) {
function formatTitle(name, options) {
if (!options.emoji)
name = name.replace(emojisRE, "");
- return `###    ${name.trim()}`;
+ return `##    ${name.trim()}`;
}
function formatSection(commits, sectionName, options) {
if (!commits.length)
@@ -198,7 +198,8 @@ function formatSection(commits, sectionName, options) {
Object.keys(scopes).sort().forEach((scope) => {
let padding = "";
let prefix = "";
- const scopeText = `**${options.scopeMap[scope] || scope}**`;
+ const url = `https://github.com/${options.github}/tree/main/packages/${scope}`
+ const scopeText = `[**@${options.github.split("/")[0]}/${options.scopeMap[scope] || scope}**](${url})`
if (scope && useScopeGroup) {
lines.push(`- ${scopeText}:`);
padding = " ";

View File

@@ -0,0 +1 @@
87de4f440a77841135f97a187e09140c6d4e6ae2

View File

@@ -1,8 +1,11 @@
nodeLinker: node-modules
checksumBehavior: update
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: "@yarnpkg/plugin-version"
yarnPath: .yarn/releases/yarn-3.2.1.cjs

View File

@@ -32,6 +32,10 @@
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier">
</a>
<img alt="Repository top language" src="https://img.shields.io/github/languages/top/semaphore-protocol/semaphore?style=flat-square">
<a href="https://www.gitpoap.io/gh/semaphore-protocol/semaphore" target="_blank">
<img src="https://public-api.gitpoap.io/v1/repo/semaphore-protocol/semaphore/badge">
</a>
</p>
<div align="center">
@@ -48,7 +52,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>
@@ -57,9 +61,7 @@
| Semaphore is a protocol, designed to be a simple and generic privacy layer for Ethereum DApps. Using zero knowledge, Ethereum users can prove their membership of a group and send signals such as votes or endorsements without revealing their original identity. |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) (NPM: `@semaphore-protocol/contracts`) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-protocol/semaphore/tree/version/1.0.0).
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
---
@@ -157,23 +159,23 @@ You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-prot
</tr>
<tr>
<td>
<a href="/packages/subgraph">
@semaphore-protocol/subgraph
<a href="/packages/data">
@semaphore-protocol/data
</a>
<a href="https://semaphore-protocol.github.io/semaphore/subgraph">
<a href="https://semaphore-protocol.github.io/semaphore/data">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/subgraph.svg?style=flat-square" alt="NPM version" />
<a href="https://npmjs.org/package/@semaphore-protocol/data">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/data.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/subgraph.svg?style=flat-square" alt="Downloads" />
<a href="https://npmjs.org/package/@semaphore-protocol/data">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/data.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>

View File

@@ -0,0 +1,7 @@
{
"types": {
"feat": { "title": "🚀 Features" },
"fix": { "title": "🐞 Bug Fixes" },
"refactor": { "title": "♻️ Refactoring" }
}
}

View File

@@ -7,7 +7,7 @@
"bugs": "https://github.com/semaphore-protocol/semaphore/issues",
"private": true,
"scripts": {
"build:libraries": "yarn workspaces foreach run build",
"build:libraries": "yarn workspaces foreach -t run build",
"compile:contracts": "yarn workspace contracts compile",
"download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts",
"test": "yarn test:libraries && yarn test:contracts",
@@ -17,9 +17,12 @@
"prettier": "prettier -c .",
"prettier:write": "prettier -w .",
"docs": "yarn workspaces foreach run docs",
"version:bump": "yarn workspaces foreach --no-private version -d ${0} && yarn version apply --all && git commit -am \"chore: v${0}\" && git tag v${0}",
"version:publish": "yarn build:libraries && yarn workspaces foreach --no-private npm publish --tolerate-republish",
"version:release": "changelogithub",
"commit": "cz",
"precommit": "lint-staged",
"postinstall": "yarn download:snark-artifacts"
"postinstall": "yarn download:snark-artifacts && husky install"
},
"keywords": [
"ethereum",
@@ -35,7 +38,8 @@
"monorepo"
],
"workspaces": [
"packages/*"
"packages/*",
"packages/contracts/contracts"
],
"packageManager": "yarn@3.2.1",
"devDependencies": {
@@ -53,6 +57,7 @@
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"babel-jest": "^27.4.6",
"changelogithub": "0.12.7",
"commitizen": "^4.2.4",
"cz-conventional-changelog": "^3.3.0",
"dotenv": "^16.0.2",
@@ -62,6 +67,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jest": "^25.7.0",
"husky": "^8.0.3",
"jest": "^27.4.1",
"jest-config": "^27.4.7",
"lint-staged": "^12.1.7",
@@ -76,5 +82,8 @@
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"resolutions": {
"changelogithub@0.12.7": "patch:changelogithub@npm:0.12.7#.yarn/patches/changelogithub-npm-0.12.7-72f348805d.patch"
}
}

View File

@@ -28,7 +28,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>

View File

@@ -0,0 +1,10 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types
# Hardhat files
cache
artifacts

View File

@@ -18,7 +18,7 @@ contract Greeter {
semaphore = ISemaphore(semaphoreAddress);
groupId = _groupId;
semaphore.createGroup(groupId, 20, 0, address(this));
semaphore.createGroup(groupId, 20, address(this));
}
function joinGroup(uint256 identityCommitment, bytes32 username) external {
@@ -35,7 +35,7 @@ contract Greeter {
uint256 nullifierHash,
uint256[8] calldata proof
) external {
semaphore.verifyProof(groupId, merkleTreeRoot, greeting, nullifierHash, groupId, proof);
semaphore.verifyProof(groupId, merkleTreeRoot, uint256(greeting), nullifierHash, groupId, proof);
emit NewGreeting(greeting);
}

View File

@@ -16,6 +16,26 @@ function getNetworks(): NetworksUserConfig {
url: process.env.ETHEREUM_URL,
chainId: 5,
accounts
},
sepolia: {
url: process.env.ETHEREUM_URL,
chainId: 11155111,
accounts
},
mumbai: {
url: process.env.ETHEREUM_URL,
chainId: 80001,
accounts
},
"optimism-goerli": {
url: process.env.ETHEREUM_URL,
chainId: 420,
accounts
},
arbitrum: {
url: process.env.ETHEREUM_URL,
chainId: 42161,
accounts
}
}
}

View File

@@ -1,9 +1,11 @@
{
"name": "@semaphore-protocol/cli-template-hardhat",
"version": "0.4.0",
"version": "3.2.3",
"description": "Semaphore Hardhat template.",
"license": "Unlicense",
"files": [
".gitignore",
".env.example",
"contracts/",
"tasks/",
"test/",
@@ -31,10 +33,10 @@
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@semaphore-protocol/group": "2.6.1",
"@semaphore-protocol/hardhat": "^0.1.0",
"@semaphore-protocol/identity": "2.6.1",
"@semaphore-protocol/proof": "2.6.1",
"@semaphore-protocol/group": "3.2.3",
"@semaphore-protocol/hardhat": "3.2.3",
"@semaphore-protocol/identity": "3.2.3",
"@semaphore-protocol/proof": "3.2.3",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@types/chai": "^4.2.0",
@@ -53,6 +55,6 @@
"typescript": ">=4.5.0"
},
"dependencies": {
"@semaphore-protocol/contracts": "2.6.1"
"@semaphore-protocol/contracts": "3.2.3"
}
}

View File

@@ -6,19 +6,11 @@ task("deploy", "Deploy a Greeter contract")
.addOptionalParam("logs", "Print the logs", true, types.boolean)
.setAction(async ({ logs, semaphore: semaphoreAddress, group: groupId }, { ethers, run }) => {
if (!semaphoreAddress) {
const { address: verifierAddress } = await run("deploy:verifier", { logs, merkleTreeDepth: 20 })
const { address } = await run("deploy:semaphore", {
logs,
verifiers: [
{
merkleTreeDepth: 20,
contractAddress: verifierAddress
}
]
const { semaphore } = await run("deploy:semaphore", {
logs
})
semaphoreAddress = address
semaphoreAddress = semaphore.address
}
const Greeter = await ethers.getContractFactory("Greeter")

View File

@@ -1,10 +1,11 @@
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof, packToSolidityProof } from "@semaphore-protocol/proof"
import { generateProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import download from "download"
import { existsSync } from "fs"
import { ethers, run } from "hardhat"
// @ts-ignore: typechain-types folder will be generated after contracts compilation
import { Greeter } from "../typechain-types"
describe("Greeter", () => {
@@ -15,7 +16,7 @@ describe("Greeter", () => {
const users: any[] = []
const groupId = "42"
const group = new Group()
const group = new Group(groupId)
before(async () => {
if (!existsSync(`${snarkArtifactsPath}/semaphore.wasm`)) {
@@ -35,8 +36,8 @@ describe("Greeter", () => {
username: ethers.utils.formatBytes32String("anon2")
})
group.addMember(users[0].identity.generateCommitment())
group.addMember(users[1].identity.generateCommitment())
group.addMember(users[0].identity.commitment)
group.addMember(users[1].identity.commitment)
})
describe("# joinGroup", () => {
@@ -57,13 +58,12 @@ describe("Greeter", () => {
wasmFilePath: `${snarkArtifactsPath}/semaphore.wasm`,
zkeyFilePath: `${snarkArtifactsPath}/semaphore.zkey`
})
const solidityProof = packToSolidityProof(fullProof.proof)
const transaction = greeter.greet(
greeting,
fullProof.publicSignals.merkleRoot,
fullProof.publicSignals.nullifierHash,
solidityProof
fullProof.merkleTreeRoot,
fullProof.nullifierHash,
fullProof.proof
)
await expect(transaction).to.emit(greeter, "NewGreeting").withArgs(greeting)

View File

@@ -40,7 +40,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>
@@ -60,7 +60,7 @@ npm i -g @semaphore-protocol/cli
or run specific commands with `npx`:
```bash
npx @semaphore-protocol/cli init my-app
npx @semaphore-protocol/cli create my-app
```
## 📜 Usage
@@ -75,7 +75,7 @@ Options:
-h, --help Display this help.
Commands:
init <project-directory> Initialize a Semaphore project with a supported template.
create <project-directory> Create a Semaphore project with a supported template.
get-groups [options] Get the list of groups from a supported network (Goerli or Arbitrum).
get-group [options] <group-id> Get the data of a group from a supported network (Goerli or Arbitrum).
help [command] Display help for a specific command.

View File

@@ -3,10 +3,7 @@
"baseUrl": ".",
"target": "es2020",
"module": "ESNext",
"allowSyntheticDefaultImports": true,
"paths": {
"@semaphore-protocol/subgraph": ["../subgraph/src/index.ts"]
}
"allowSyntheticDefaultImports": true
},
"include": ["src"]
}

View File

@@ -1,7 +1,7 @@
{
"name": "@semaphore-protocol/cli",
"type": "module",
"version": "0.4.0",
"version": "3.2.3",
"description": "A command line tool to set up your Semaphore project and get group data.",
"license": "MIT",
"bin": {
@@ -34,18 +34,23 @@
"devDependencies": {
"@types/clear": "^0.1.2",
"@types/figlet": "^1.5.5",
"@types/semver": "^7.3.13",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.31.2",
"ts-node": "^10.9.1",
"typedoc": "^0.22.11"
},
"dependencies": {
"@semaphore-protocol/subgraph": "2.6.1",
"@semaphore-protocol/data": "3.2.3",
"axios": "^1.3.2",
"boxen": "^7.0.1",
"chalk": "^5.1.2",
"commander": "^9.4.1",
"download": "^8.0.0",
"figlet": "^1.5.2",
"inquirer": "^9.1.4",
"log-symbols": "^5.1.0",
"ora": "^6.1.2"
"ora": "^6.1.2",
"semver": "^7.3.8"
}
}

View File

@@ -0,0 +1,48 @@
import axios from "axios"
import boxen from "boxen"
import chalk from "chalk"
import { execSync } from "child_process"
import { lt as semverLt } from "semver"
const cliRegistryURL = "https://registry.npmjs.org/-/package/@semaphore-protocol/cli/dist-tags"
/**
* Checks the registry directly via the API, if that fails, tries the slower `npm view [package] version` command.
* This is important for users in environments where direct access to npm is blocked by a firewall, and packages are
* provided exclusively via a private registry.
* @param currentVersion The current version of the CLI.
*/
export default async function checkLatestVersion(currentVersion: string) {
let latestVersion: string
try {
const { data } = await axios.get(cliRegistryURL)
latestVersion = data.latest
} catch {
try {
latestVersion = execSync("npm view @semaphore-protocol/cli version").toString().trim()
} catch {
latestVersion = null
}
}
if (latestVersion && semverLt(currentVersion, latestVersion)) {
console.info("\n")
console.info(
boxen(
chalk.white(
`Update available ${chalk.gray(currentVersion)} -> ${chalk.green(
latestVersion
)} \n\n You are currently using @semaphore-protocol/cli ${chalk.gray(
currentVersion
)} which is behind the latest release ${chalk.green(latestVersion)} \n\n Run ${chalk.cyan(
"npm install -g @semaphore-protocol/cli@latest"
)} to get the latest version`
),
{ padding: 1, borderColor: "yellow", textAlignment: "center" }
)
)
console.info("")
}
}

View File

@@ -1,17 +1,21 @@
import { Subgraph } from "@semaphore-protocol/subgraph"
import { SemaphoreSubgraph } from "@semaphore-protocol/data"
import chalk from "chalk"
import { program } from "commander"
import download from "download"
import figlet from "figlet"
import { existsSync, readFileSync, renameSync } from "fs"
import inquirer from "inquirer"
import logSymbols from "log-symbols"
import { dirname } from "path"
import { fileURLToPath } from "url"
import checkLatestVersion from "./checkLatestVersion.js"
import Spinner from "./spinner.js"
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
const { description, version } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"))
const supportedNetworks = ["goerli", "mumbai", "optimism-goerli", "arbitrum"]
program
.name("semaphore")
.description(description)
@@ -27,11 +31,22 @@ program
})
program
.command("init")
.description("Initialize a Semaphore project with a supported template.")
.argument("<project-directory>", "Directory of the project.")
.command("create")
.description("Create a Semaphore project with a supported template.")
.argument("[project-directory]", "Directory of the project.")
// .option("-t, --template <template-name>", "Supported Semaphore template.", "hardhat")
.allowExcessArguments(false)
.action(async (projectDirectory) => {
if (!projectDirectory) {
const { projectName } = await inquirer.prompt({
name: "projectName",
type: "input",
message: "What is your project name?",
default: "my-app"
})
projectDirectory = projectName
}
const currentDirectory = process.cwd()
const spinner = new Spinner(`Creating your project in ${chalk.green(`./${projectDirectory}`)}`)
const templateURL = `https://registry.npmjs.org/@semaphore-protocol/cli-template-hardhat/-/cli-template-hardhat-${version}.tgz`
@@ -43,13 +58,15 @@ program
spinner.start()
await checkLatestVersion(version)
await download(templateURL, currentDirectory, { extract: true })
renameSync(`${currentDirectory}/package`, `${currentDirectory}/${projectDirectory}`)
spinner.stop()
console.info(` ${logSymbols.success}`, `Your project is ready!\n`)
console.info(`\n ${logSymbols.success}`, `Your project is ready!\n`)
console.info(` Please, install your dependencies by running:\n`)
console.info(` ${chalk.cyan("cd")} ${projectDirectory}`)
console.info(` ${chalk.cyan("npm i")}\n`)
@@ -64,58 +81,134 @@ program
.map((s) => ` ${chalk.cyan(`npm run ${s}`)}`)
.join("\n")}\n`
)
console.info(` See the README.md file to understand how to use them!\n`)
}
})
program
.command("get-groups")
.description("Get the list of groups from a supported network (goerli or arbitrum).")
.option("-n, --network <network-name>", "Supported Ethereum network.", "goerli")
.option("-n, --network <network-name>", "Supported Ethereum network.")
.allowExcessArguments(false)
.action(async ({ network }) => {
if (!["goerli", "arbitrum"].includes(network)) {
if (!network) {
const { selectedNetwork } = await inquirer.prompt({
name: "selectedNetwork",
type: "list",
message: "Select one of our supported networks:",
default: supportedNetworks[0],
choices: supportedNetworks
})
network = selectedNetwork
}
if (!supportedNetworks.includes(network)) {
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
return
}
const subgraph = new Subgraph(network)
const subgraph = new SemaphoreSubgraph(network)
const spinner = new Spinner("Fetching groups")
spinner.start()
try {
const groups = await subgraph.getGroups()
const groupIds = await subgraph.getGroupIds()
spinner.stop()
if (groups.length === 0) {
console.info(` ${logSymbols.error}`, "error: there are no groups in this network\n")
if (groupIds.length === 0) {
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
return
}
const content = `${groups.map(({ id }: any) => ` - ${id}`).join("\n")}`
const content = `\n${groupIds.map((id: any) => ` - ${id}`).join("\n")}`
console.info(`${content}\n`)
} catch (error) {
spinner.stop()
console.info(` ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
}
})
program
.command("get-group")
.description("Get the data of a group from a supported network (Goerli or Arbitrum).")
.argument("<group-id>", "Identifier of the group.")
.option("-n, --network <network-name>", "Supported Ethereum network.", "goerli")
.option("--members", "Show group members.")
.option("--signals", "Show group signals.")
.argument("[group-id]", "Identifier of the group.")
.option("-n, --network <network-name>", "Supported Ethereum network.")
.option("-m, --members", "Show group members.")
.option("-s, --signals", "Show group signals.")
.allowExcessArguments(false)
.action(async (groupId, { network, members, signals }) => {
if (!["goerli", "arbitrum"].includes(network)) {
if (!network) {
const { selectedNetwork } = await inquirer.prompt({
name: "selectedNetwork",
type: "list",
message: "Select one of our supported networks:",
default: supportedNetworks[0],
choices: supportedNetworks
})
network = selectedNetwork
}
if (!groupId) {
const subgraphGroups = new SemaphoreSubgraph(network)
const spinnerGroups = new Spinner("Fetching groups")
spinnerGroups.start()
try {
const groups = await subgraphGroups.getGroups()
spinnerGroups.stop()
if (groups.length === 0) {
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
return
}
const groupIds = groups.map(({ id }: any) => id)
const { selectedGroupId } = await inquirer.prompt({
name: "selectedGroupId",
type: "list",
message: "Select one of the following existing group ids:",
choices: groupIds
})
groupId = selectedGroupId
} catch (error) {
spinnerGroups.stop()
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
return
}
}
if (!members && !signals) {
const { showMembers } = await inquirer.prompt({
name: "showMembers",
type: "confirm",
message: "Do you want to show members?",
default: false
})
members = showMembers
const { showSignals } = await inquirer.prompt({
name: "showSignals",
type: "confirm",
message: "Do you want to show signals?",
default: false
})
signals = showSignals
}
if (!supportedNetworks.includes(network)) {
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
return
}
const subgraph = new Subgraph(network)
const subgraph = new SemaphoreSubgraph(network)
const spinner = new Spinner(`Fetching group ${groupId}`)
spinner.start()
@@ -126,7 +219,7 @@ program
spinner.stop()
if (!group) {
console.info(` ${logSymbols.error}`, "error: the group does not exist\n")
console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n")
return
}
@@ -151,12 +244,12 @@ program
.join("\n")}`
}
console.info(`${content}\n`)
console.info(`\n${content}\n`)
} catch (error) {
spinner.stop()
console.info(` ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
}
})
program.parse()
program.parse(process.argv)

View File

@@ -20,6 +20,6 @@ export default class Spinner {
stop() {
this.ora.stop()
process.stdout.cursorTo(0)
process.stdout.moveCursor(0, -1)
}
}

View File

@@ -4,10 +4,7 @@
"target": "es2020",
"module": "ESNext",
"moduleResolution": "Node16",
"allowSyntheticDefaultImports": true,
"paths": {
"@semaphore-protocol/subgraph": ["../subgraph/src/index.ts"]
}
"allowSyntheticDefaultImports": true
},
"include": ["src", "rollup.config.ts"]
}

View File

@@ -34,7 +34,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>
@@ -92,29 +92,29 @@ yarn test:report-gas
### Deploy contracts
Deploy a verifier contract with depth = 20:
Deploy the `Semaphore.sol` contract without any parameter:
```bash
yarn deploy:verifier --depth 20
yarn deploy:semaphore
```
Deploy the `Semaphore.sol` contract with one verifier:
or deploy it by providing the addresses of the contracts/libraries on which it depends:
```bash
yarn deploy:semaphore --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]'
yarn deploy:semaphore --semaphoreVerifier <address>
```
Deploy all verifiers and Semaphore contract:
> **Note**
> Run `yarn deploy:semaphore --help` to see the complete list.
If you want to deploy your contract in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as an option:
```bash
yarn deploy:all
```
If you want to deploy contracts in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as option:
```bash
yarn deploy:all --network goerli
yarn deploy:all --network localhost
yarn deploy:semaphore --network goerli
yarn deploy:semaphore --network sepolia
yarn deploy:semaphore --network mumbai
yarn deploy:semaphore --network optimism-goerli
yarn deploy:semaphore --network arbitrum
```
If you want to deploy contracts on Goerli or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file.

View File

@@ -9,7 +9,7 @@
pragma solidity ^0.8.4;
library Pairing {
error Semaphore__InvalidProof();
error InvalidProof();
// The prime q in the base field F_q for G1
uint256 constant BASE_MODULUS = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
@@ -56,7 +56,7 @@ library Pairing {
// Validate input or revert
if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) {
revert Semaphore__InvalidProof();
revert InvalidProof();
}
// We know p.Y > 0 and p.Y < BASE_MODULUS.
@@ -82,7 +82,7 @@ library Pairing {
}
if (!success) {
revert Semaphore__InvalidProof();
revert InvalidProof();
}
}
@@ -92,7 +92,7 @@ library Pairing {
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
if (s >= SCALAR_MODULUS) {
revert Semaphore__InvalidProof();
revert InvalidProof();
}
uint256[3] memory input;
@@ -109,7 +109,7 @@ library Pairing {
}
if (!success) {
revert Semaphore__InvalidProof();
revert InvalidProof();
}
}
@@ -120,7 +120,7 @@ library Pairing {
// By EIP-197 all input is verified to be less than the BASE_MODULUS and form elements in their
// respective groups of the right order.
if (p1.length != p2.length) {
revert Semaphore__InvalidProof();
revert InvalidProof();
}
uint256 elements = p1.length;
@@ -145,7 +145,7 @@ library Pairing {
}
if (!success || out[0] != 1) {
revert Semaphore__InvalidProof();
revert InvalidProof();
}
}
}

View File

@@ -1,9 +1,10 @@
{
"name": "@semaphore-protocol/contracts",
"version": "2.6.1",
"version": "3.2.3",
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
"license": "MIT",
"files": [
"Semaphore.sol",
"**/*.sol"
],
"keywords": [

View File

@@ -1,24 +1,7 @@
{
"verifiers": {
"Verifier16": "0x6143ECd9Fd1A00EDe1046d456f8aab53a7D71609",
"Verifier17": "0xAc12fFFE354D6446eb50dd33E683B78FED73Fb02",
"Verifier18": "0x610aeF0F2da3CD1C8bDefe4BDB434Ee146E0C701",
"Verifier19": "0x5477725177035bbC9d70443eb921D29749D6FCb4",
"Verifier20": "0x3fB2C0988a37b76e760c44e6516aF720935f3136",
"Verifier21": "0xDc8f6B8A42836d4566256f4c6C53131DFD127DF8",
"Verifier22": "0x6962b5e706be5278eeCb01c286b50A48484632f2",
"Verifier23": "0x41e4796Bd89B4BF04013b559c93fC32E9a2BdF6B",
"Verifier24": "0xD528B1D1408ab3583af4694F92b0aFEbE33d5b60",
"Verifier25": "0x1683a27EF9c10c5286dB56412E1272cD0Ca733e7",
"Verifier26": "0x78194bB665d1E33b97eE45B1A755c15717E94C00",
"Verifier27": "0x997Dac00E6701Ef7F3518280E5a9922801126E42",
"Verifier28": "0xDd3C7f4cBA2467aE41c0F614A3c3E24bC80268c6",
"Verifier29": "0xe53eF12093933D5df5691EAbA3821bD1c1EB60Cd",
"Verifier30": "0x7FeA07c536ABBB0E7FB3c833376EE4EaDc21340e",
"Verifier31": "0xe4539a592df18936202480FBe77E47DE012F2178",
"Verifier32": "0x98c90845A7870e215cBd7265DDC653E6c07032F4"
},
"Semaphore": "0x86337c87A56117f8264bbaBA70e5a522C6E8A604",
"PoseidonT3": "0xe0c8d1e53D9Bfc9071F6564755FCFf6cC0dB61d0",
"IncrementalBinaryTree": "0x91cD2B8573629d00BeC72EA1188d446897BD3948"
"Pairing": "0xE3a4C2FE9f025405cA6F60f6E960B4558604A74C",
"SemaphoreVerifier": "0xCAbeED6cB96a287000aBd834b0B79c05e6Ea4d07",
"Poseidon": "0xe0c8d1e53D9Bfc9071F6564755FCFf6cC0dB61d0",
"IncrementalBinaryTree": "0xcDF8efE6334c68aF283C83f2F14648da51fcfFb0",
"Semaphore": "0x72dca3c971136bf47BACF16A141f0fcfAC925aeC"
}

View File

@@ -1,24 +1,8 @@
{
"verifiers": {
"Verifier16": "0xA5253ba39381Aa99c4C2C5A4D5C2deC036d06629",
"Verifier17": "0xe0418A5f8fBF051D6cbc41Ff29855Dd2a02201Ab",
"Verifier18": "0x7CdB3336d7d7c55Bce0FB1508594C54521656797",
"Verifier19": "0xbd870921d8A5398a3314C950d1fc63b8C3AB190B",
"Verifier20": "0x2a96c5696F85e3d2aa918496806B5c5a4D93E099",
"Verifier21": "0x5Ec7d851a52A2a25CEc528F42a7ACA8EcF4667Cd",
"Verifier22": "0x919d3d9c05FA7411e334deA5a763354fC7B6aA5b",
"Verifier23": "0x63917b00a6dA7865bEfdd107AfC83CC2e6BDE552",
"Verifier24": "0xd05CAd7d940114c1419098EE3cEA0776ab510E7D",
"Verifier25": "0x6D9862e6140D94E932d94c8BcE74a0BDD0ea5ACb",
"Verifier26": "0x8c29e0b77e32f704F03eeCE01c041192A5EB6c77",
"Verifier27": "0x066cC22f8CA2A8D90D7Ff77D8a10A27e629c9c4C",
"Verifier28": "0x698F9507f504E2BD238be7da56E8D9fee60C6D15",
"Verifier29": "0xbBfC2E201C3c3c6F50063c3Edb4746c6Fcb36346",
"Verifier30": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1",
"Verifier31": "0x133b69Ce47BF20C49368354914DF47519Ca6cCFE",
"Verifier32": "0xe2978F79cb4AF62e5C990EE5c7E12fb22ee22e2D"
},
"Semaphore": "0x5259d32659F1806ccAfcE593ED5a89eBAb85262f",
"Semaphore": "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7",
"PoseidonT3": "0xe0A452533853310C371b50Bd91BB9DCC8961350F",
"IncrementalBinaryTree": "0x61AE89E372492e53D941DECaaC9821649fa9B236"
"IncrementalBinaryTree": "0x9Ed9f58CA9212Ddf0377C8C4Cd089748F9337820",
"Pairing": "0xE9c41c912CF750D79Cf304a196d4Bc8Dfd626C86",
"SemaphoreVerifier": "0x66e772B0B8Ee1c24E4b6aC99A3A82C77f431792E",
"Poseidon": "0xe0A452533853310C371b50Bd91BB9DCC8961350F"
}

View File

@@ -0,0 +1,7 @@
{
"Pairing": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
"SemaphoreVerifier": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
"Poseidon": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
"IncrementalBinaryTree": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F",
"Semaphore": "0xF864ABa335073e01234c9a88888BfFfa965650bD"
}

View File

@@ -0,0 +1,7 @@
{
"Pairing": "0xEFD83f827FA5B0496359D817c6CD8a5AA5D2aCe5",
"SemaphoreVerifier": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
"Poseidon": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
"IncrementalBinaryTree": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
"Semaphore": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
}

View File

@@ -0,0 +1,7 @@
{
"Pairing": "0xEFD83f827FA5B0496359D817c6CD8a5AA5D2aCe5",
"SemaphoreVerifier": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
"Poseidon": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
"IncrementalBinaryTree": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
"Semaphore": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
}

View File

@@ -30,8 +30,23 @@ function getNetworks(): NetworksUserConfig {
chainId: 5,
accounts
},
sepolia: {
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155111,
accounts
},
mumbai: {
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
chainId: 80001,
accounts
},
"optimism-goerli": {
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
chainId: 420,
accounts
},
arbitrum: {
url: "https://arb1.arbitrum.io/rpc",
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
chainId: 42161,
accounts
}

View File

@@ -17,7 +17,7 @@
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
"@nomiclabs/hardhat-ethers": "^2.0.6",
"@nomiclabs/hardhat-etherscan": "^3.1.0",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@semaphore-protocol/group": "workspace:packages/group",
"@semaphore-protocol/identity": "workspace:packages/identity",
"@semaphore-protocol/proof": "workspace:packages/proof",

View File

@@ -1,33 +1,21 @@
import { readFileSync, writeFileSync } from "fs"
type DeployedContracts = {
Pairing?: string
SemaphoreVerifier?: string
Poseidon?: string
IncrementalBinaryTree?: string
Semaphore?: string
Pairing: string
SemaphoreVerifier: string
Poseidon: string
IncrementalBinaryTree: string
Semaphore: string
}
export function getDeployedContracts(network: string | undefined): DeployedContracts | null {
try {
return JSON.parse(readFileSync(`./deployed-contracts/${network}.json`, "utf8"))
} catch (error) {
return {}
return null
}
}
export function saveDeployedContracts(network: string | undefined, newDeployedContracts: DeployedContracts) {
const deployedContracts = getDeployedContracts(network)
writeFileSync(
`./deployed-contracts/${network}.json`,
JSON.stringify(
{
...deployedContracts,
...newDeployedContracts
},
null,
4
)
)
export function saveDeployedContracts(network: string | undefined, deployedContracts: DeployedContracts) {
writeFileSync(`./deployed-contracts/${network}.json`, JSON.stringify(deployedContracts, null, 4))
}

View File

@@ -1,24 +1,26 @@
import { hardhatArguments, run } from "hardhat"
import { getDeployedContracts } from "./utils"
async function verify(address: string, constructorArguments?: any[]): Promise<void> {
try {
await run("verify:verify", {
address,
constructorArguments
})
} catch (error) {
console.error(error)
}
}
async function main() {
const deployedContracts = getDeployedContracts(hardhatArguments.network)
await run("verify:verify", {
address: deployedContracts.IncrementalBinaryTree
})
await run("verify:verify", {
address: deployedContracts.Semaphore
})
await run("verify:verify", {
address: deployedContracts.Pairing
})
await run("verify:verify", {
address: deployedContracts.SemaphoreVerifier
})
if (deployedContracts) {
await verify(deployedContracts.IncrementalBinaryTree)
await verify(deployedContracts.Pairing)
await verify(deployedContracts.SemaphoreVerifier)
await verify(deployedContracts.Semaphore, [deployedContracts.SemaphoreVerifier])
}
}
main()

View File

@@ -2,7 +2,7 @@
/* eslint-disable jest/valid-expect */
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { FullProof, generateProof, packToSolidityProof, SolidityProof } from "@semaphore-protocol/proof"
import { FullProof, generateProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import { constants, Signer } from "ethers"
import { ethers, run } from "hardhat"
@@ -238,7 +238,6 @@ describe("Semaphore", () => {
group.addMembers(members)
let fullProof: FullProof
let solidityProof: SolidityProof
before(async () => {
await semaphoreContract.addMembers(groupId, [members[1], members[2]])
@@ -247,7 +246,6 @@ describe("Semaphore", () => {
wasmFilePath,
zkeyFilePath
})
solidityProof = packToSolidityProof(fullProof.proof)
})
it("Should not verify a proof if the group does not exist", async () => {
@@ -268,45 +266,45 @@ describe("Semaphore", () => {
it("Should throw an exception if the proof is not valid", async () => {
const transaction = semaphoreContract.verifyProof(
groupId,
group.root,
signal,
fullProof.publicSignals.nullifierHash,
fullProof.merkleTreeRoot,
fullProof.signal,
fullProof.nullifierHash,
0,
solidityProof
fullProof.proof
)
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "Semaphore__InvalidProof")
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof")
})
it("Should verify a proof for an onchain group correctly", async () => {
const transaction = semaphoreContract.verifyProof(
groupId,
group.root,
signal,
fullProof.publicSignals.nullifierHash,
fullProof.publicSignals.merkleTreeRoot,
solidityProof
fullProof.merkleTreeRoot,
fullProof.signal,
fullProof.nullifierHash,
fullProof.merkleTreeRoot,
fullProof.proof
)
await expect(transaction)
.to.emit(semaphoreContract, "ProofVerified")
.withArgs(
groupId,
group.root,
fullProof.publicSignals.nullifierHash,
fullProof.publicSignals.merkleTreeRoot,
signal
fullProof.merkleTreeRoot,
fullProof.nullifierHash,
fullProof.merkleTreeRoot,
fullProof.signal
)
})
it("Should not verify the same proof for an onchain group twice", async () => {
const transaction = semaphoreContract.verifyProof(
groupId,
group.root,
signal,
fullProof.publicSignals.nullifierHash,
fullProof.publicSignals.merkleTreeRoot,
solidityProof
fullProof.merkleTreeRoot,
fullProof.signal,
fullProof.nullifierHash,
fullProof.merkleTreeRoot,
fullProof.proof
)
await expect(transaction).to.be.revertedWithCustomError(
@@ -325,15 +323,14 @@ describe("Semaphore", () => {
wasmFilePath,
zkeyFilePath
})
const solidityProof = packToSolidityProof(fullProof.proof)
const transaction = semaphoreContract.verifyProof(
groupId,
group.root,
signal,
fullProof.publicSignals.nullifierHash,
0,
solidityProof
fullProof.merkleTreeRoot,
fullProof.signal,
fullProof.nullifierHash,
fullProof.merkleTreeRoot,
fullProof.proof
)
await expect(transaction).to.be.revertedWithCustomError(

View File

@@ -1,7 +1,7 @@
/* eslint-disable jest/valid-expect */
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof, packToSolidityProof, PublicSignals, SolidityProof } from "@semaphore-protocol/proof"
import { FullProof, generateProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import { Signer } from "ethers"
import { ethers, run } from "hardhat"
@@ -142,27 +142,23 @@ describe("SemaphoreVoting", () => {
group.addMembers([identity.commitment, BigInt(1)])
let solidityProof: SolidityProof
let publicSignals: PublicSignals
let fullProof: FullProof
before(async () => {
await semaphoreVotingContract.connect(accounts[1]).addVoter(pollIds[1], BigInt(1))
await semaphoreVotingContract.connect(accounts[1]).startPoll(pollIds[1], encryptionKey)
await semaphoreVotingContract.createPoll(pollIds[2], coordinator, treeDepth)
const fullProof = await generateProof(identity, group, pollIds[1], vote, {
fullProof = await generateProof(identity, group, pollIds[1], vote, {
wasmFilePath,
zkeyFilePath
})
publicSignals = fullProof.publicSignals
solidityProof = packToSolidityProof(fullProof.proof)
})
it("Should not cast a vote if the poll is not ongoing", async () => {
const transaction = semaphoreVotingContract
.connect(accounts[1])
.castVote(vote, publicSignals.nullifierHash, pollIds[2], solidityProof)
.castVote(vote, fullProof.nullifierHash, pollIds[2], fullProof.proof)
await expect(transaction).to.be.revertedWithCustomError(
semaphoreVotingContract,
@@ -173,15 +169,15 @@ describe("SemaphoreVoting", () => {
it("Should not cast a vote if the proof is not valid", async () => {
const transaction = semaphoreVotingContract
.connect(accounts[1])
.castVote(vote, 0, pollIds[1], solidityProof)
.castVote(vote, 0, pollIds[1], fullProof.proof)
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "Semaphore__InvalidProof")
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof")
})
it("Should cast a vote", async () => {
const transaction = semaphoreVotingContract
.connect(accounts[1])
.castVote(vote, publicSignals.nullifierHash, pollIds[1], solidityProof)
.castVote(vote, fullProof.nullifierHash, pollIds[1], fullProof.proof)
await expect(transaction).to.emit(semaphoreVotingContract, "VoteAdded").withArgs(pollIds[1], vote)
})
@@ -189,7 +185,7 @@ describe("SemaphoreVoting", () => {
it("Should not cast a vote twice", async () => {
const transaction = semaphoreVotingContract
.connect(accounts[1])
.castVote(vote, publicSignals.nullifierHash, pollIds[1], solidityProof)
.castVote(vote, fullProof.nullifierHash, pollIds[1], fullProof.proof)
await expect(transaction).to.be.revertedWithCustomError(
semaphoreVotingContract,

View File

@@ -1,7 +1,7 @@
/* eslint-disable jest/valid-expect */
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof, packToSolidityProof, PublicSignals, SolidityProof } from "@semaphore-protocol/proof"
import { FullProof, generateProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import { Signer, utils } from "ethers"
import { ethers, run } from "hardhat"
@@ -143,8 +143,7 @@ describe("SemaphoreWhistleblowing", () => {
group.addMembers([identity.commitment, BigInt(1)])
let solidityProof: SolidityProof
let publicSignals: PublicSignals
let fullProof: FullProof
before(async () => {
await semaphoreWhistleblowingContract.createEntity(entityIds[1], editor, treeDepth)
@@ -153,27 +152,24 @@ describe("SemaphoreWhistleblowing", () => {
.addWhistleblower(entityIds[1], identity.commitment)
await semaphoreWhistleblowingContract.connect(accounts[1]).addWhistleblower(entityIds[1], BigInt(1))
const fullProof = await generateProof(identity, group, entityIds[1], leak, {
fullProof = await generateProof(identity, group, entityIds[1], leak, {
wasmFilePath,
zkeyFilePath
})
publicSignals = fullProof.publicSignals
solidityProof = packToSolidityProof(fullProof.proof)
})
it("Should not publish a leak if the proof is not valid", async () => {
const transaction = semaphoreWhistleblowingContract
.connect(accounts[1])
.publishLeak(leak, 0, entityIds[1], solidityProof)
.publishLeak(leak, 0, entityIds[1], fullProof.proof)
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "Semaphore__InvalidProof")
await expect(transaction).to.be.revertedWithCustomError(pairingContract, "InvalidProof")
})
it("Should publish a leak", async () => {
const transaction = semaphoreWhistleblowingContract
.connect(accounts[1])
.publishLeak(leak, publicSignals.nullifierHash, entityIds[1], solidityProof)
.publishLeak(leak, fullProof.nullifierHash, entityIds[1], fullProof.proof)
await expect(transaction)
.to.emit(semaphoreWhistleblowingContract, "LeakPublished")

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Ethereum Foundation
Copyright (c) 2023 Ethereum Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

159
packages/data/README.md Normal file
View File

@@ -0,0 +1,159 @@
<p align="center">
<h1 align="center">
Semaphore data
</h1>
<p align="center">A library to query Semaphore contracts.</p>
</p>
<p align="center">
<a href="https://github.com/semaphore-protocol">
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@semaphore-protocol/data">
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/data?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@semaphore-protocol/data">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/data.svg?style=flat-square" />
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
</a>
<a href="https://prettier.io/">
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
</a>
</p>
<div align="center">
<h4>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
👥 Contributing
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
🤝 Code of conduct
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>
</div>
| This library allows you to query the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/contracts/Semaphore.sol) contract data (i.e. groups) using the [Semaphore subgraph](https://github.com/semaphore-protocol/subgraph) or Ethers. It can be used on Node.js and browsers. |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## 🛠 Install
### npm or yarn
Install the `@semaphore-protocol/data` package with npm:
```bash
npm i @semaphore-protocol/data
```
or yarn:
```bash
yarn add @semaphore-protocol/data
```
## 📜 Usage
\# **new SemaphoreSubgraph**(networkOrSubgraphURL: _Network_ | _string_ = "goerli"): _SemaphoreSubgraph_
```typescript
import { SemaphoreSubgraph } from "@semaphore-protocol/data"
const semaphoreSubgraph = new SemaphoreSubgraph()
// or:
const semaphoreSubgraph = new SemaphoreSubgraph("arbitrum")
// or:
const semaphoreSubgraph = new SemaphoreSubgraph(
"https://api.studio.thegraph.com/query/14377/<your-subgraph>/<your-version>"
)
```
\# **getGroupIds**(): _Promise\<string[]>_
```typescript
const groupIds = await semaphoreSubgraph.getGroupIds()
```
\# **getGroups**(options?: _GroupOptions_): _Promise\<GroupResponse[]>_
```typescript
const groups = await semaphoreSubgraph.getGroups()
// or
const groups = await semaphoreSubgraph.getGroups({ members: true, verifiedProofs: true })
```
\# **getGroup**(groupId: _string_, options?: _GroupOptions_): _Promise\<GroupResponse>_
```typescript
const group = await semaphoreSubgraph.getGroup("42")
// or
const { members, verifiedProofs } = semaphoreSubgraph.getGroup("42", { members: true, verifiedProofs: true })
```
\# **new Ethers**(networkOrEthereumURL: Network | string = "goerli", options: EthersOptions = {}): _SemaphoreEthers_
```typescript
import { SemaphoreEthers } from "@semaphore-protocol/data"
const semaphoreEthers = new SemaphoreEthers()
// or:
const semaphoreEthers = new SemaphoreEthers("homestead", {
address: "semaphore-address",
startBlock: 0
})
// or:
const semaphoreEthers = new SemaphoreEthers("http://localhost:8545", {
address: "semaphore-address"
})
```
\# **getGroupIds**(): _Promise\<string[]>_
```typescript
const groupIds = await semaphoreEthers.getGroupIds()
```
\# **getGroup**(groupId: _string_): _Promise\<GroupResponse>_
```typescript
const group = await semaphoreEthers.getGroup("42")
```
\# **getGroupAdmin**(groupId: _string_): _Promise\<string>_
```typescript
const admin = await semaphoreEthers.getGroupAdmin("42")
```
\# **getGroupMembers**(groupId: _string_): _Promise\<string[]>_
```typescript
const members = await semaphoreEthers.getGroupMembers("42")
```
\# **getGroupVerifiedProofs**(groupId: _string_): _Promise\<any[]>_
```typescript
const verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs()
```

View File

@@ -1,11 +1,8 @@
{
"name": "@semaphore-protocol/subgraph",
"version": "2.6.1",
"name": "@semaphore-protocol/data",
"version": "3.2.3",
"description": "A library to query Semaphore contracts.",
"license": "MIT",
"iife": "dist/index.js",
"unpkg": "dist/index.min.js",
"jsdelivr": "dist/index.min.js",
"main": "dist/index.node.js",
"exports": {
"import": "./dist/index.mjs",
@@ -19,7 +16,7 @@
"README.md"
],
"repository": "https://github.com/semaphore-protocol/semaphore",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/subgraph",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/data",
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
@@ -27,18 +24,20 @@
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "yarn build",
"docs": "typedoc src/index.ts --out ../../docs/subgraph"
"docs": "typedoc src/index.ts --out ../../docs/data"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@rollup/plugin-json": "^6.0.0",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.31.2",
"typedoc": "^0.22.11"
},
"dependencies": {
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/providers": "^5.7.0",
"axios": "^0.27.2"
}
}

View File

@@ -1,10 +1,9 @@
import json from "@rollup/plugin-json"
import * as fs from "fs"
import cleanup from "rollup-plugin-cleanup"
import { terser } from "rollup-plugin-terser"
import typescript from "rollup-plugin-typescript2"
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
const name = pkg.name.substr(1).replace(/[-/]./g, (x: string) => x.toUpperCase()[1])
const banner = `/**
* @module ${pkg.name}
* @version ${pkg.version}
@@ -17,25 +16,12 @@ const banner = `/**
export default {
input: "src/index.ts",
output: [
{
file: pkg.iife,
name,
format: "iife",
globals: { axios: "axios" },
banner
},
{
file: pkg.unpkg,
name,
format: "iife",
globals: { axios: "axios" },
plugins: [terser({ output: { preamble: banner } })]
},
{ file: pkg.exports.require, format: "cjs", banner, exports: "auto" },
{ file: pkg.exports.import, format: "es", banner }
],
external: Object.keys(pkg.dependencies),
plugins: [
json(),
typescript({ tsconfig: "./build.tsconfig.json", useTsconfigDeclarationDir: true }),
cleanup({ comments: "jsdoc" })
]

View File

@@ -0,0 +1,286 @@
import SemaphoreEthers from "./ethers"
import getEvents from "./getEvents"
jest.mock("./getEvents", () => ({
__esModule: true,
default: jest.fn()
}))
jest.mock("@ethersproject/contracts", () => ({
__esModule: true,
Contract: jest.fn(
() =>
({
getMerkleTreeRoot: () => "222",
getNumberOfMerkleTreeLeaves: () => ({
toNumber: () => 2
})
} as any)
)
}))
const getEventsMocked = getEvents as jest.MockedFunction<typeof getEvents>
describe("SemaphoreEthers", () => {
let semaphore: SemaphoreEthers
describe("# SemaphoreEthers", () => {
it("Should instantiate a SemaphoreEthers object with different networks", () => {
semaphore = new SemaphoreEthers()
const semaphore1 = new SemaphoreEthers("arbitrum")
const semaphore2 = new SemaphoreEthers("matic")
const semaphore3 = new SemaphoreEthers("homestead", {
address: "0x0000000000000000000000000000000000000000",
startBlock: 0
})
expect(semaphore.network).toBe("goerli")
expect(semaphore.contract).toBeInstanceOf(Object)
expect(semaphore1.network).toBe("arbitrum")
expect(semaphore2.network).toBe("maticmum")
expect(semaphore3.network).toBe("homestead")
expect(semaphore3.options.startBlock).toBe(0)
expect(semaphore3.options.address).toContain("0x000000")
})
it("Should instantiate a SemaphoreEthers object with different providers", () => {
const semaphore1 = new SemaphoreEthers("homestead", {
provider: "infura",
address: "0x0000000000000000000000000000000000000000",
apiKey: "1234567890"
})
const semaphore2 = new SemaphoreEthers("homestead", {
provider: "etherscan",
address: "0x0000000000000000000000000000000000000000"
})
const semaphore3 = new SemaphoreEthers("homestead", {
provider: "alchemy",
address: "0x0000000000000000000000000000000000000000"
})
const semaphore4 = new SemaphoreEthers("homestead", {
provider: "cloudflare",
address: "0x0000000000000000000000000000000000000000"
})
const semaphore5 = new SemaphoreEthers("homestead", {
provider: "pocket",
address: "0x0000000000000000000000000000000000000000"
})
const semaphore6 = new SemaphoreEthers("homestead", {
provider: "ankr",
address: "0x0000000000000000000000000000000000000000"
})
expect(semaphore1.options.provider).toBe("infura")
expect(semaphore1.options.apiKey).toBe("1234567890")
expect(semaphore2.options.provider).toBe("etherscan")
expect(semaphore3.options.provider).toBe("alchemy")
expect(semaphore4.options.provider).toBe("cloudflare")
expect(semaphore5.options.provider).toBe("pocket")
expect(semaphore6.options.provider).toBe("ankr")
})
it("Should instantiate a SemaphoreEthers object with a custom URL", () => {
const semaphore1 = new SemaphoreEthers("http://localhost:8545", {
address: "0x0000000000000000000000000000000000000000"
})
expect(semaphore1.network).toBe("http://localhost:8545")
})
it("Should throw an error if the network is not supported by Semaphore yet and there's no address", () => {
const fun = () => new SemaphoreEthers("homestead")
expect(fun).toThrow("You should provide a Semaphore contract address for this network")
})
it("Should throw an error if the provider is not supported", () => {
const fun = () =>
new SemaphoreEthers("goerli", {
provider: "hello" as any
})
expect(fun).toThrow("Provider 'hello' is not supported")
})
})
describe("# getGroupIds", () => {
it("Should return all the existing groups", async () => {
getEventsMocked.mockReturnValueOnce(Promise.resolve([["32"], ["42"]]))
const groupIds = await semaphore.getGroupIds()
expect(groupIds).toContain("42")
})
})
describe("# getGroup", () => {
it("Should return a specific group", async () => {
getEventsMocked.mockReturnValueOnce(
Promise.resolve([
{
merkleTreeDepth: "20",
zeroValue: "111"
}
])
)
const group = await semaphore.getGroup("42")
expect(group.merkleTree.depth).toBe("20")
expect(group.merkleTree.root).toBe("222")
expect(group.merkleTree.zeroValue).toContain("111")
})
it("Should throw an error if the group does not exist", async () => {
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
const fun = () => semaphore.getGroup("666")
await expect(fun).rejects.toThrow("Group '666' not found")
})
})
describe("# getGroupAdmin", () => {
it("Should return a group admin", async () => {
getEventsMocked.mockReturnValueOnce(
Promise.resolve([
{
newAdmin: "0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F"
}
])
)
const admin = await semaphore.getGroupAdmin("42")
expect(admin).toBe("0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F")
})
it("Should throw an error if the group does not exist", async () => {
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
const fun = () => semaphore.getGroupAdmin("666")
await expect(fun).rejects.toThrow("Group '666' not found")
})
})
describe("# getGroupMembers", () => {
it("Should return a list of group members", async () => {
getEventsMocked.mockReturnValueOnce(
Promise.resolve([
{
merkleTreeDepth: "20",
zeroValue: "0"
}
])
)
getEventsMocked.mockReturnValueOnce(
Promise.resolve([
{
index: "0",
merkleTreeRoot: "223",
blockNumber: 3
},
{
index: "2",
merkleTreeRoot: "224",
blockNumber: 4
}
])
)
getEventsMocked.mockReturnValueOnce(
Promise.resolve([
{
index: "1",
newIdentityCommitment: "113",
merkleTreeRoot: "225",
blockNumber: 3
},
{
index: "2",
newIdentityCommitment: "114",
merkleTreeRoot: "226",
blockNumber: 3
}
])
)
getEventsMocked.mockReturnValueOnce(
Promise.resolve([
{
index: "0",
identityCommitment: "110",
merkleTreeRoot: "220",
blockNumber: 0
},
{
index: "1",
identityCommitment: "111",
merkleTreeRoot: "221",
blockNumber: 1
},
{
index: "2",
identityCommitment: "112",
merkleTreeRoot: "222",
blockNumber: 2
},
{
index: "3",
identityCommitment: "113",
merkleTreeRoot: "223",
blockNumber: 3
}
])
)
const members = await semaphore.getGroupMembers("42")
expect(members[0]).toBe("0")
expect(members[1]).toBe("113")
expect(members[2]).toBe("0")
})
it("Should throw an error if the group does not exist", async () => {
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
const fun = () => semaphore.getGroupMembers("666")
await expect(fun).rejects.toThrow("Group '666' not found")
})
})
describe("# getGroupVerifiedProofs", () => {
it("Should return a list of group verified proofs", async () => {
getEventsMocked.mockReturnValueOnce(
Promise.resolve([
{
merkleTreeDepth: "20",
zeroValue: "0"
}
])
)
getEventsMocked.mockReturnValueOnce(
Promise.resolve([
{
signal: "111",
merkleTreeRoot: "112",
externalNullifier: "113",
nullifierHash: "114"
}
])
)
const [verifiedProof] = await semaphore.getGroupVerifiedProofs("42")
expect(verifiedProof.signal).toContain("111")
})
it("Should throw an error if the group does not exist", async () => {
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
const fun = () => semaphore.getGroupVerifiedProofs("666")
await expect(fun).rejects.toThrow("Group '666' not found")
})
})
})

269
packages/data/src/ethers.ts Normal file
View File

@@ -0,0 +1,269 @@
import { Contract } from "@ethersproject/contracts"
import {
AlchemyProvider,
AnkrProvider,
CloudflareProvider,
EtherscanProvider,
InfuraProvider,
JsonRpcProvider,
PocketProvider,
Provider
} from "@ethersproject/providers"
import checkParameter from "./checkParameter"
import getEvents from "./getEvents"
import SemaphoreABI from "./semaphoreABI.json"
import { EthersOptions, GroupResponse, Network } from "./types"
export default class SemaphoreEthers {
private _network: Network | string
private _options: EthersOptions
private _contract: Contract
/**
* Initializes the Ethers object with an Ethereum network or custom URL.
* @param networkOrEthereumURL Ethereum network or custom URL.
* @param options Ethers options.
*/
constructor(networkOrEthereumURL: Network | string = "goerli", options: EthersOptions = {}) {
checkParameter(networkOrEthereumURL, "networkOrSubgraphURL", "string")
if (options.provider) {
checkParameter(options.provider, "provider", "string")
} else if (!networkOrEthereumURL.startsWith("http")) {
options.provider = "infura"
}
if (options.apiKey) {
checkParameter(options.apiKey, "apiKey", "string")
}
if (networkOrEthereumURL === "matic") {
networkOrEthereumURL = "maticmum"
}
switch (networkOrEthereumURL) {
case "arbitrum":
options.address = "0x72dca3c971136bf47BACF16A141f0fcfAC925aeC"
options.startBlock = 54934350
break
case "maticmum":
options.address = "0xF864ABa335073e01234c9a88888BfFfa965650bD"
options.startBlock = 32902215
break
case "goerli":
options.address = "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7"
options.startBlock = 8255063
break
default:
if (options.address === undefined) {
throw new Error(`You should provide a Semaphore contract address for this network`)
}
if (options.startBlock === undefined) {
options.startBlock = 0
}
}
let provider: Provider
switch (options.provider) {
case "infura":
provider = new InfuraProvider(networkOrEthereumURL, options.apiKey)
break
case "alchemy":
provider = new AlchemyProvider(networkOrEthereumURL, options.apiKey)
break
case "cloudflare":
provider = new CloudflareProvider(networkOrEthereumURL, options.apiKey)
break
case "etherscan":
provider = new EtherscanProvider(networkOrEthereumURL, options.apiKey)
break
case "pocket":
provider = new PocketProvider(networkOrEthereumURL, options.apiKey)
break
case "ankr":
provider = new AnkrProvider(networkOrEthereumURL, options.apiKey)
break
default:
if (!networkOrEthereumURL.startsWith("http")) {
throw new Error(`Provider '${options.provider}' is not supported`)
}
provider = new JsonRpcProvider(networkOrEthereumURL)
}
this._network = networkOrEthereumURL
this._options = options
this._contract = new Contract(options.address, SemaphoreABI, provider)
}
/**
* Returns the Ethereum network or custom URL.
* @returns Ethereum network or custom URL.
*/
get network(): Network | string {
return this._network
}
/**
* Returns the Ethers options.
* @returns Ethers options.
*/
get options(): EthersOptions {
return this._options
}
/**
* Returns the contract object.
* @returns Contract object.
*/
get contract(): Contract {
return this._contract
}
/**
* Returns the list of group ids.
* @returns List of group ids.
*/
async getGroupIds(): Promise<string[]> {
const groups = await getEvents(this._contract, "GroupCreated", [], this._options.startBlock)
return groups.map((event: any) => event[0].toString())
}
/**
* Returns a specific group.
* @param groupId Group id.
* @returns Specific group.
*/
async getGroup(groupId: string): Promise<GroupResponse> {
checkParameter(groupId, "groupId", "string")
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
if (!groupCreatedEvent) {
throw new Error(`Group '${groupId}' not found`)
}
const merkleTreeRoot = await this._contract.getMerkleTreeRoot(groupId)
const numberOfLeaves = await this._contract.getNumberOfMerkleTreeLeaves(groupId)
const group: GroupResponse = {
id: groupId,
merkleTree: {
depth: groupCreatedEvent.merkleTreeDepth.toString(),
zeroValue: groupCreatedEvent.zeroValue.toString(),
numberOfLeaves: numberOfLeaves.toNumber(),
root: merkleTreeRoot.toString()
}
}
return group
}
/**
* Returns a group admin.
* @param groupId Group id.
* @returns Group admin.
*/
async getGroupAdmin(groupId: string): Promise<string> {
checkParameter(groupId, "groupId", "string")
const groupAdminUpdatedEvents = await getEvents(
this._contract,
"GroupAdminUpdated",
[groupId],
this._options.startBlock
)
if (groupAdminUpdatedEvents.length === 0) {
throw new Error(`Group '${groupId}' not found`)
}
return groupAdminUpdatedEvents[groupAdminUpdatedEvents.length - 1].newAdmin.toString()
}
/**
* Returns a list of group members.
* @param groupId Group id.
* @returns Group members.
*/
async getGroupMembers(groupId: string): Promise<string[]> {
checkParameter(groupId, "groupId", "string")
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
if (!groupCreatedEvent) {
throw new Error(`Group '${groupId}' not found`)
}
const zeroValue = groupCreatedEvent.zeroValue.toString()
const memberRemovedEvents = await getEvents(
this._contract,
"MemberRemoved",
[groupId],
this._options.startBlock
)
const memberUpdatedEvents = await getEvents(
this._contract,
"MemberUpdated",
[groupId],
this._options.startBlock
)
const groupUpdates = new Map<string, [number, string]>()
for (const { blockNumber, index, newIdentityCommitment } of memberUpdatedEvents) {
groupUpdates.set(index.toString(), [blockNumber, newIdentityCommitment.toString()])
}
for (const { blockNumber, index } of memberRemovedEvents) {
const groupUpdate = groupUpdates.get(index.toString())
if (!groupUpdate || (groupUpdate && groupUpdate[0] < blockNumber)) {
groupUpdates.set(index.toString(), [blockNumber, zeroValue])
}
}
const memberAddedEvents = await getEvents(this._contract, "MemberAdded", [groupId], this._options.startBlock)
const members: string[] = []
for (const { index, identityCommitment } of memberAddedEvents) {
const groupUpdate = groupUpdates.get(index.toString())
const member = groupUpdate ? groupUpdate[1].toString() : identityCommitment.toString()
members.push(member)
}
return members
}
/**
* Returns a list of group verified proofs.
* @param groupId Group id.
* @returns Group verified proofs.
*/
async getGroupVerifiedProofs(groupId: string): Promise<any> {
checkParameter(groupId, "groupId", "string")
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
if (!groupCreatedEvent) {
throw new Error(`Group '${groupId}' not found`)
}
const proofVerifiedEvents = await getEvents(
this._contract,
"ProofVerified",
[groupId],
this._options.startBlock
)
return proofVerifiedEvents.map((event) => ({
signal: event.signal.toString(),
merkleTreeRoot: event.merkleTreeRoot.toString(),
externalNullifier: event.externalNullifier.toString(),
nullifierHash: event.externalNullifier.toString()
}))
}
}

View File

@@ -0,0 +1,22 @@
/* istanbul ignore file */
import { Contract } from "@ethersproject/contracts"
/**
* Returns the list of events of a contract with possible filters.
* @param contract Contract instance.
* @param eventName Name of the event.
* @param filterArgs Filter arguments.
* @param startBlock Block from which to start fetching.
* @returns List of contract events.
*/
export default async function getEvents(
contract: Contract,
eventName: string,
filterArgs: any[] = [],
startBlock: number = 0
): Promise<any[]> {
const filter = contract.filters[eventName](...filterArgs)
const events = await contract.queryFilter(filter, startBlock)
return events.map(({ args, blockNumber }) => ({ ...args, blockNumber }))
}

View File

@@ -8,9 +8,10 @@ import { Network } from "./types"
export default function getURL(network: Network): string {
switch (network) {
case "goerli":
return `https://api.thegraph.com/subgraphs/name/semaphore-protocol/goerli-5259d3`
case "mumbai":
case "optimism-goerli":
case "arbitrum":
return `https://api.thegraph.com/subgraphs/name/semaphore-protocol/arbitrum-86337c`
return `https://api.studio.thegraph.com/query/14377/semaphore-${network}/v3.2.0`
default:
throw new TypeError(`Network '${network}' is not supported`)
}

View File

@@ -0,0 +1,5 @@
import SemaphoreSubgraph from "./subgraph"
import SemaphoreEthers from "./ethers"
export { SemaphoreSubgraph, SemaphoreEthers }
export * from "./types"

View File

@@ -8,7 +8,13 @@ import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
*/
/* istanbul ignore next */
export default async function request(url: string, config?: AxiosRequestConfig): Promise<any> {
const { data }: AxiosResponse<any> = await axios(url, config)
const { data }: AxiosResponse<any> = await axios(url, {
headers: {
"Content-Type": "application/json",
...config?.headers
},
...config
})
return data?.data
}

View File

@@ -0,0 +1,575 @@
[
{
"inputs": [
{
"internalType": "contract ISemaphoreVerifier",
"name": "_verifier",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "Semaphore__CallerIsNotTheGroupAdmin",
"type": "error"
},
{
"inputs": [],
"name": "Semaphore__GroupAlreadyExists",
"type": "error"
},
{
"inputs": [],
"name": "Semaphore__GroupDoesNotExist",
"type": "error"
},
{
"inputs": [],
"name": "Semaphore__MerkleTreeDepthIsNotSupported",
"type": "error"
},
{
"inputs": [],
"name": "Semaphore__MerkleTreeRootIsExpired",
"type": "error"
},
{
"inputs": [],
"name": "Semaphore__MerkleTreeRootIsNotPartOfTheGroup",
"type": "error"
},
{
"inputs": [],
"name": "Semaphore__YouAreUsingTheSameNillifierTwice",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "oldAdmin",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newAdmin",
"type": "address"
}
],
"name": "GroupAdminUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "merkleTreeDepth",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "zeroValue",
"type": "uint256"
}
],
"name": "GroupCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "oldMerkleTreeDuration",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newMerkleTreeDuration",
"type": "uint256"
}
],
"name": "GroupMerkleTreeDurationUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "identityCommitment",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "merkleTreeRoot",
"type": "uint256"
}
],
"name": "MemberAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "identityCommitment",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "merkleTreeRoot",
"type": "uint256"
}
],
"name": "MemberRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "index",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "identityCommitment",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newIdentityCommitment",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "merkleTreeRoot",
"type": "uint256"
}
],
"name": "MemberUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "merkleTreeRoot",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "externalNullifier",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "nullifierHash",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "signal",
"type": "uint256"
}
],
"name": "ProofVerified",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "identityCommitment",
"type": "uint256"
}
],
"name": "addMember",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "uint256[]",
"name": "identityCommitments",
"type": "uint256[]"
}
],
"name": "addMembers",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "merkleTreeDepth",
"type": "uint256"
},
{
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"internalType": "uint256",
"name": "merkleTreeDuration",
"type": "uint256"
}
],
"name": "createGroup",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "merkleTreeDepth",
"type": "uint256"
},
{
"internalType": "address",
"name": "admin",
"type": "address"
}
],
"name": "createGroup",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
}
],
"name": "getMerkleTreeDepth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
}
],
"name": "getMerkleTreeRoot",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
}
],
"name": "getNumberOfMerkleTreeLeaves",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "groups",
"outputs": [
{
"internalType": "address",
"name": "admin",
"type": "address"
},
{
"internalType": "uint256",
"name": "merkleTreeDuration",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "identityCommitment",
"type": "uint256"
},
{
"internalType": "uint256[]",
"name": "proofSiblings",
"type": "uint256[]"
},
{
"internalType": "uint8[]",
"name": "proofPathIndices",
"type": "uint8[]"
}
],
"name": "removeMember",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "address",
"name": "newAdmin",
"type": "address"
}
],
"name": "updateGroupAdmin",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "newMerkleTreeDuration",
"type": "uint256"
}
],
"name": "updateGroupMerkleTreeDuration",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "identityCommitment",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "newIdentityCommitment",
"type": "uint256"
},
{
"internalType": "uint256[]",
"name": "proofSiblings",
"type": "uint256[]"
},
{
"internalType": "uint8[]",
"name": "proofPathIndices",
"type": "uint8[]"
}
],
"name": "updateMember",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "verifier",
"outputs": [
{
"internalType": "contract ISemaphoreVerifier",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "groupId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "merkleTreeRoot",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "signal",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "nullifierHash",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "externalNullifier",
"type": "uint256"
},
{
"internalType": "uint256[8]",
"name": "proof",
"type": "uint256[8]"
}
],
"name": "verifyProof",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -1,5 +1,5 @@
import request from "./request"
import Subgraph from "./subgraph"
import SemaphoreSubgraph from "./subgraph"
jest.mock("./request", () => ({
__esModule: true,
@@ -8,28 +8,58 @@ jest.mock("./request", () => ({
const requestMocked = request as jest.MockedFunction<typeof request>
describe("Subgraph", () => {
let subgraph: Subgraph
describe("SemaphoreSubgraph", () => {
let semaphore: SemaphoreSubgraph
describe("# Subgraph", () => {
it("Should instantiate a subgraph object", () => {
subgraph = new Subgraph("goerli")
const subgraph1 = new Subgraph()
describe("# SemaphoreSubgraph", () => {
it("Should instantiate a SemaphoreSubgraph object", () => {
semaphore = new SemaphoreSubgraph()
const semaphore1 = new SemaphoreSubgraph("arbitrum")
expect(subgraph.url).toContain("goerli")
expect(subgraph1.url).toContain("arbitrum")
expect(semaphore.url).toContain("goerli")
expect(semaphore1.url).toContain("arbitrum")
})
it("Should instantiate a SemaphoreSubgraph object using URL", () => {
const url = "https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.2.0"
const semaphore1 = new SemaphoreSubgraph(url)
expect(semaphore1.url).toBe(url)
})
it("Should throw an error if there is a wrong network", () => {
const fun = () => new Subgraph("wrong" as any)
const fun = () => new SemaphoreSubgraph("wrong" as any)
expect(fun).toThrow("Network 'wrong' is not supported")
})
it("Should throw an error if the network parameter type is wrong", () => {
const fun = () => new Subgraph(33 as any)
it("Should throw an error if the networkOrSubgraphURL parameter type is wrong", () => {
const fun = () => new SemaphoreSubgraph(33 as any)
expect(fun).toThrow("Parameter 'network' is not a string")
expect(fun).toThrow("Parameter 'networkOrSubgraphURL' is not a string")
})
})
describe("# getGroupIds", () => {
it("Should return all the existing group ids", async () => {
requestMocked.mockImplementationOnce(() =>
Promise.resolve({
groups: [
{
id: "1"
},
{
id: "2"
}
]
})
)
const expectedValue = await semaphore.getGroupIds()
expect(expectedValue).toBeDefined()
expect(Array.isArray(expectedValue)).toBeTruthy()
expect(expectedValue).toContainEqual("1")
})
})
@@ -52,7 +82,7 @@ describe("Subgraph", () => {
})
)
const expectedValue = await subgraph.getGroups()
const expectedValue = await semaphore.getGroups()
expect(expectedValue).toBeDefined()
expect(Array.isArray(expectedValue)).toBeTruthy()
@@ -69,7 +99,7 @@ describe("Subgraph", () => {
})
it("Should throw an error if the options parameter type is wrong", async () => {
const fun = () => subgraph.getGroups(1 as any)
const fun = () => semaphore.getGroups(1 as any)
await expect(fun).rejects.toThrow("Parameter 'options' is not an object")
})
@@ -116,7 +146,7 @@ describe("Subgraph", () => {
})
)
const expectedValue = await subgraph.getGroups({
const expectedValue = await semaphore.getGroups({
members: true,
verifiedProofs: true
})
@@ -151,6 +181,44 @@ describe("Subgraph", () => {
]
})
})
it("Should return groups by applying filters", async () => {
requestMocked.mockImplementationOnce(() =>
Promise.resolve({
groups: [
{
id: "1",
merkleTree: {
depth: 20,
zeroValue: 0,
numberOfLeaves: 2,
root: "2"
},
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
}
]
})
)
const expectedValue = await semaphore.getGroups({
filters: {
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
}
})
expect(expectedValue).toBeDefined()
expect(Array.isArray(expectedValue)).toBeTruthy()
expect(expectedValue).toContainEqual({
id: "1",
merkleTree: {
depth: 20,
zeroValue: 0,
numberOfLeaves: 2,
root: "2"
},
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
})
})
})
describe("# getGroup", () => {
@@ -172,7 +240,7 @@ describe("Subgraph", () => {
})
)
const expectedValue = await subgraph.getGroup("1")
const expectedValue = await semaphore.getGroup("1")
expect(expectedValue).toBeDefined()
expect(expectedValue).toEqual({
@@ -188,7 +256,7 @@ describe("Subgraph", () => {
})
it("Should throw an error if the options parameter type is wrong", async () => {
const fun = () => subgraph.getGroup("1", 1 as any)
const fun = () => semaphore.getGroup("1", 1 as any)
await expect(fun).rejects.toThrow("Parameter 'options' is not an object")
})
@@ -235,7 +303,7 @@ describe("Subgraph", () => {
})
)
const expectedValue = await subgraph.getGroup("1", {
const expectedValue = await semaphore.getGroup("1", {
members: true,
verifiedProofs: true
})

View File

@@ -2,19 +2,25 @@ import { AxiosRequestConfig } from "axios"
import checkParameter from "./checkParameter"
import getURL from "./getURL"
import request from "./request"
import { GroupOptions, Network } from "./types"
import { GroupResponse, GroupOptions, Network } from "./types"
import { jsDateToGraphqlDate } from "./utils"
export default class Subgraph {
export default class SemaphoreSubgraph {
private _url: string
/**
* Initializes the subgraph object with one of the supported networks.
* @param network Supported Semaphore network.
* Initializes the subgraph object with one of the supported networks or a custom URL.
* @param networkOrSubgraphURL Supported Semaphore network or custom Subgraph URL.
*/
constructor(network: Network = "arbitrum") {
checkParameter(network, "network", "string")
constructor(networkOrSubgraphURL: Network | string = "goerli") {
checkParameter(networkOrSubgraphURL, "networkOrSubgraphURL", "string")
this._url = getURL(network)
if (networkOrSubgraphURL.startsWith("http")) {
this._url = networkOrSubgraphURL
return
}
this._url = getURL(networkOrSubgraphURL as Network)
}
/**
@@ -25,12 +31,33 @@ export default class Subgraph {
return this._url
}
/**
* Returns the list of group ids.
* @returns List of group ids.
*/
async getGroupIds(): Promise<string[]> {
const config: AxiosRequestConfig = {
method: "post",
data: JSON.stringify({
query: `{
groups {
id
}
}`
})
}
const { groups } = await request(this._url, config)
return groups.map((group: any) => group.id)
}
/**
* Returns the list of groups.
* @param options Options to select the group parameters.
* @returns List of groups.
*/
async getGroups(options: GroupOptions = {}): Promise<any[]> {
async getGroups(options: GroupOptions = {}): Promise<GroupResponse[]> {
checkParameter(options, "options", "object")
const { members = false, verifiedProofs = false } = options
@@ -38,11 +65,35 @@ export default class Subgraph {
checkParameter(members, "members", "boolean")
checkParameter(verifiedProofs, "verifiedProofs", "boolean")
let filtersQuery = ""
if (options.filters) {
const { admin, timestamp, timestampGte, timestampLte } = options.filters
const filterFragments = []
if (admin) {
filterFragments.push(`admin: "${admin}"`)
}
/* istanbul ignore next */
if (timestamp) {
filterFragments.push(`timestamp: "${jsDateToGraphqlDate(timestamp)}"`)
} else if (timestampGte) {
filterFragments.push(`timestamp_gte: "${jsDateToGraphqlDate(timestampGte)}"`)
} else if (timestampLte) {
filterFragments.push(`timestamp_lte: "${jsDateToGraphqlDate(timestampLte)}"`)
}
if (filterFragments.length > 0) {
filtersQuery = `(where: {${filterFragments.join(", ")}})`
}
}
const config: AxiosRequestConfig = {
method: "post",
data: JSON.stringify({
query: `{
groups {
groups ${filtersQuery} {
id
merkleTree {
root
@@ -91,7 +142,7 @@ export default class Subgraph {
* @param options Options to select the group parameters.
* @returns Specific group.
*/
async getGroup(groupId: string, options: GroupOptions = {}): Promise<any> {
async getGroup(groupId: string, options: Omit<GroupOptions, "filters"> = {}): Promise<GroupResponse> {
checkParameter(groupId, "groupId", "string")
checkParameter(options, "options", "object")

View File

@@ -0,0 +1,48 @@
export type Network =
| "homestead"
| "matic"
| "goerli"
| "arbitrum"
| "maticmum"
| "mumbai"
| "arbitrum-goerli"
| "optimism"
| "optimism-goerli"
| "sepolia"
export type GroupOptions = {
members?: boolean
verifiedProofs?: boolean
filters?: {
admin?: string
timestamp?: Date
timestampGte?: Date
timestampLte?: Date
}
}
export type GroupResponse = {
id: string
merkleTree: {
root: string
depth: number
zeroValue: string
numberOfLeaves: number
}
admin?: string
members?: string[]
verifiedProofs?: {
signal: string
merkleTreeRoot: string
externalNullifier: string
nullifierHash: string
timestamp?: string
}[]
}
export type EthersOptions = {
address?: string
startBlock?: number
provider?: "etherscan" | "infura" | "alchemy" | "cloudflare" | "pocket" | "ankr"
apiKey?: string
}

View File

@@ -0,0 +1,11 @@
import { jsDateToGraphqlDate } from "./utils"
describe("Utils", () => {
describe("# jsDateToGraphqlDate", () => {
it("Should convert a JS date to the GraphQL date", async () => {
const date = jsDateToGraphqlDate(new Date("2020-01-01"))
expect(date).toBe(1577836800)
})
})
})

View File

@@ -0,0 +1,4 @@
// eslint-disable-next-line import/prefer-default-export
export function jsDateToGraphqlDate(date: Date): number {
return Math.round(date.getTime() / 1000)
}

View File

@@ -40,7 +40,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/group",
"version": "2.6.1",
"version": "3.2.3",
"description": "A library to create and manage Semaphore groups.",
"license": "MIT",
"main": "dist/index.node.js",
@@ -30,6 +30,9 @@
"access": "public"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"poseidon-lite": "^0.1.0",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.31.2",
"typedoc": "^0.22.11"
@@ -38,7 +41,6 @@
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/keccak256": "^5.7.0",
"@zk-kit/incremental-merkle-tree": "1.0.0",
"poseidon-lite": "^0.0.2"
"@zk-kit/incremental-merkle-tree": "1.0.0"
}
}

View File

@@ -1,6 +1,8 @@
import typescript from "rollup-plugin-typescript2"
import commonjs from "@rollup/plugin-commonjs"
import { nodeResolve } from "@rollup/plugin-node-resolve"
import * as fs from "fs"
import cleanup from "rollup-plugin-cleanup"
import typescript from "rollup-plugin-typescript2"
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
const banner = `/**
@@ -24,6 +26,8 @@ export default {
tsconfig: "./build.tsconfig.json",
useTsconfigDeclarationDir: true
}),
commonjs(),
nodeResolve(),
cleanup({ comments: "jsdoc" })
]
}

View File

@@ -6,6 +6,7 @@ describe("Group", () => {
it("Should create a group", () => {
const group = new Group(1)
expect(group.id).toBe(1)
expect(group.root.toString()).toContain("103543")
expect(group.depth).toBe(20)
expect(group.zeroValue).toBe(hash(1))

View File

@@ -1,29 +1,40 @@
import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree"
import poseidon from "poseidon-lite"
import { poseidon2 } from "poseidon-lite/poseidon2"
import hash from "./hash"
import { Member } from "./types"
import { BigNumberish } from "./types"
export default class Group {
private _id: BigNumberish
merkleTree: IncrementalMerkleTree
/**
* Initializes the group with the tree depth and the zero value.
* @param groupId Group identifier.
* Initializes the group with the group id and the tree depth.
* @param id Group identifier.
* @param treeDepth Tree depth.
*/
constructor(groupId: Member, treeDepth = 20) {
constructor(id: BigNumberish, treeDepth = 20) {
if (treeDepth < 16 || treeDepth > 32) {
throw new Error("The tree depth must be between 16 and 32")
}
this.merkleTree = new IncrementalMerkleTree(poseidon, treeDepth, hash(groupId), 2)
this._id = id
this.merkleTree = new IncrementalMerkleTree(poseidon2, treeDepth, hash(id), 2)
}
/**
* Returns the id of the group.
* @returns Group id.
*/
get id(): BigNumberish {
return this._id
}
/**
* Returns the root hash of the tree.
* @returns Root hash.
*/
get root(): Member {
get root(): BigNumberish {
return this.merkleTree.root
}
@@ -39,7 +50,7 @@ export default class Group {
* Returns the zero value of the tree.
* @returns Tree zero value.
*/
get zeroValue(): Member {
get zeroValue(): BigNumberish {
return this.merkleTree.zeroes[0]
}
@@ -47,7 +58,7 @@ export default class Group {
* Returns the members (i.e. identity commitments) of the group.
* @returns List of members.
*/
get members(): Member[] {
get members(): BigNumberish[] {
return this.merkleTree.leaves
}
@@ -56,7 +67,7 @@ export default class Group {
* @param member Group member.
* @returns Index of the member.
*/
indexOf(member: Member): number {
indexOf(member: BigNumberish): number {
return this.merkleTree.indexOf(member)
}
@@ -64,7 +75,7 @@ export default class Group {
* Adds a new member to the group.
* @param member New member.
*/
addMember(member: Member) {
addMember(member: BigNumberish) {
this.merkleTree.insert(BigInt(member))
}
@@ -72,7 +83,7 @@ export default class Group {
* Adds new members to the group.
* @param members New members.
*/
addMembers(members: Member[]) {
addMembers(members: BigNumberish[]) {
for (const member of members) {
this.addMember(member)
}
@@ -83,7 +94,7 @@ export default class Group {
* @param index Index of the member to be updated.
* @param member New member value.
*/
updateMember(index: number, member: Member) {
updateMember(index: number, member: BigNumberish) {
this.merkleTree.update(index, member)
}

View File

@@ -1 +1 @@
export type Member = string | number | bigint
export type BigNumberish = string | number | bigint

View File

@@ -2,7 +2,7 @@
<h1 align="center">
Semaphore Hardhat plugin
</h1>
<p align="center">A Semaphore Hardhat plugin to deploy verifiers and Semaphore contracts.</p>
<p align="center">A Semaphore Hardhat plugin to deploy Semaphore contracts.</p>
</p>
<p align="center">
@@ -40,14 +40,14 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>
</div>
| This Hardhat plugin provides two simple tasks that can be used to deploy verifiers and Semaphore contracts without any additional configuration. |
| ------------------------------------------------------------------------------------------------------------------------------------------------ |
| This Hardhat plugin provides two simple tasks that can be used to deploy Semaphore contracts without any additional configuration. |
| ---------------------------------------------------------------------------------------------------------------------------------- |
## 🛠 Install
@@ -88,21 +88,18 @@ import { task, types } from "hardhat/config"
task("deploy", "Deploy a Greeter contract")
.addOptionalParam("logs", "Print the logs", true, types.boolean)
.setAction(async ({ logs }, { ethers, run }) => {
const { address: verifierAddress } = await run("deploy:verifier", { logs, merkleTreeDepth: 20 })
const { address: semaphoreAddress } = await run("deploy:semaphore", {
logs,
verifiers: [
{
merkleTreeDepth: 20,
contractAddress: verifierAddress
}
]
const { semaphore } = await run("deploy:semaphore", {
logs
})
// Or:
// const { semaphoreVerifier } = await run("deploy:semaphore-verifier", {
// logs
// })
const Greeter = await ethers.getContractFactory("Greeter")
const greeter = await Greeter.deploy(semaphoreAddress)
const greeter = await Greeter.deploy(semaphore.address)
await greeter.deployed()

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/hardhat",
"version": "0.1.0",
"version": "3.2.3",
"description": "A Semaphore Hardhat plugin to deploy verifiers and Semaphore contract.",
"license": "MIT",
"main": "dist/index.node.js",
@@ -38,7 +38,7 @@
},
"dependencies": {
"@nomiclabs/hardhat-ethers": "^2.1.1",
"@semaphore-protocol/contracts": "^2.5.0",
"@semaphore-protocol/contracts": "3.2.3",
"circomlibjs": "^0.0.8",
"ethers": "^5.7.1",
"hardhat-dependency-compiler": "^1.1.3"

View File

@@ -4,27 +4,12 @@ import { HardhatConfig, HardhatUserConfig } from "hardhat/types"
import "hardhat-dependency-compiler"
import "@nomiclabs/hardhat-ethers"
import "./tasks/deploy-semaphore"
import "./tasks/deploy-verifier"
import "./tasks/deploy-semaphore-verifier"
extendConfig((config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
config.dependencyCompiler.paths = [
"@semaphore-protocol/contracts/verifiers/Verifier16.sol",
"@semaphore-protocol/contracts/verifiers/Verifier17.sol",
"@semaphore-protocol/contracts/verifiers/Verifier18.sol",
"@semaphore-protocol/contracts/verifiers/Verifier19.sol",
"@semaphore-protocol/contracts/verifiers/Verifier20.sol",
"@semaphore-protocol/contracts/verifiers/Verifier21.sol",
"@semaphore-protocol/contracts/verifiers/Verifier22.sol",
"@semaphore-protocol/contracts/verifiers/Verifier23.sol",
"@semaphore-protocol/contracts/verifiers/Verifier24.sol",
"@semaphore-protocol/contracts/verifiers/Verifier25.sol",
"@semaphore-protocol/contracts/verifiers/Verifier26.sol",
"@semaphore-protocol/contracts/verifiers/Verifier27.sol",
"@semaphore-protocol/contracts/verifiers/Verifier28.sol",
"@semaphore-protocol/contracts/verifiers/Verifier29.sol",
"@semaphore-protocol/contracts/verifiers/Verifier30.sol",
"@semaphore-protocol/contracts/verifiers/Verifier31.sol",
"@semaphore-protocol/contracts/verifiers/Verifier32.sol",
"@semaphore-protocol/contracts/base/Pairing.sol",
"@semaphore-protocol/contracts/base/SemaphoreVerifier.sol",
"@semaphore-protocol/contracts/Semaphore.sol"
]

View File

@@ -0,0 +1,38 @@
import { task, types } from "hardhat/config"
task("deploy:semaphore-verifier", "Deploy a SemaphoreVerifier contract")
.addOptionalParam<boolean>("pairing", "Pairing library address", undefined, types.string)
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
.setAction(async ({ logs, pairing: pairingAddress }, { ethers }): Promise<any> => {
if (!pairingAddress) {
const PairingFactory = await ethers.getContractFactory("Pairing")
const pairing = await PairingFactory.deploy()
await pairing.deployed()
if (logs) {
console.info(`Pairing library has been deployed to: ${pairing.address}`)
}
pairingAddress = pairing.address
}
const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", {
libraries: {
Pairing: pairingAddress
}
})
const semaphoreVerifier = await SemaphoreVerifierFactory.deploy()
await semaphoreVerifier.deployed()
if (logs) {
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`)
}
return {
semaphoreVerifier,
pairingAddress
}
})

View File

@@ -1,51 +1,114 @@
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
import { Contract } from "ethers"
import { task, types } from "hardhat/config"
task("deploy:semaphore", "Deploy a Semaphore contract")
.addParam("verifiers", "Tree depths and verifier addresses", [], types.json)
.addOptionalParam<boolean>("pairing", "Pairing library address", undefined, types.string)
.addOptionalParam<boolean>("semaphoreVerifier", "SemaphoreVerifier contract address", undefined, types.string)
.addOptionalParam<boolean>("poseidon", "Poseidon library address", undefined, types.string)
.addOptionalParam<boolean>(
"incrementalBinaryTree",
"IncrementalBinaryTree library address",
undefined,
types.string
)
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
.setAction(async ({ logs, verifiers }, { ethers }): Promise<Contract> => {
const poseidonABI = poseidonContract.generateABI(2)
const poseidonBytecode = poseidonContract.createCode(2)
.setAction(
async (
{
logs,
pairing: pairingAddress,
semaphoreVerifier: semaphoreVerifierAddress,
poseidon: poseidonAddress,
incrementalBinaryTree: incrementalBinaryTreeAddress
},
{ ethers }
): Promise<any> => {
if (!semaphoreVerifierAddress) {
if (!pairingAddress) {
const PairingFactory = await ethers.getContractFactory("Pairing")
const pairing = await PairingFactory.deploy()
const [signer] = await ethers.getSigners()
await pairing.deployed()
const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer)
const poseidonLib = await PoseidonLibFactory.deploy()
if (logs) {
console.info(`Pairing library has been deployed to: ${pairing.address}`)
}
await poseidonLib.deployed()
pairingAddress = pairing.address
}
if (logs) {
console.info(`Poseidon library has been deployed to: ${poseidonLib.address}`)
}
const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", {
libraries: {
Pairing: pairingAddress
}
})
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
libraries: {
PoseidonT3: poseidonLib.address
const semaphoreVerifier = await SemaphoreVerifierFactory.deploy()
await semaphoreVerifier.deployed()
if (logs) {
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`)
}
semaphoreVerifierAddress = semaphoreVerifier.address
}
})
const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy()
await incrementalBinaryTreeLib.deployed()
if (!incrementalBinaryTreeAddress) {
if (!poseidonAddress) {
const poseidonABI = poseidonContract.generateABI(2)
const poseidonBytecode = poseidonContract.createCode(2)
if (logs) {
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
}
const [signer] = await ethers.getSigners()
const SemaphoreContractFactory = await ethers.getContractFactory("Semaphore", {
libraries: {
IncrementalBinaryTree: incrementalBinaryTreeLib.address
const PoseidonFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer)
const poseidon = await PoseidonFactory.deploy()
await poseidon.deployed()
if (logs) {
console.info(`Poseidon library has been deployed to: ${poseidon.address}`)
}
poseidonAddress = poseidon.address
}
const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
libraries: {
PoseidonT3: poseidonAddress
}
})
const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy()
await incrementalBinaryTree.deployed()
if (logs) {
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`)
}
incrementalBinaryTreeAddress = incrementalBinaryTree.address
}
})
const semaphoreContract = await SemaphoreContractFactory.deploy(verifiers)
const SemaphoreFactory = await ethers.getContractFactory("Semaphore", {
libraries: {
IncrementalBinaryTree: incrementalBinaryTreeAddress
}
})
await semaphoreContract.deployed()
const semaphore = await SemaphoreFactory.deploy(semaphoreVerifierAddress)
if (logs) {
console.info(`Semaphore contract has been deployed to: ${semaphoreContract.address}`)
await semaphore.deployed()
if (logs) {
console.info(`Semaphore contract has been deployed to: ${semaphore.address}`)
}
return {
semaphore,
pairingAddress,
semaphoreVerifierAddress,
poseidonAddress,
incrementalBinaryTreeAddress
}
}
return semaphoreContract
})
)

View File

@@ -1,19 +0,0 @@
import { Contract } from "ethers"
import { task, types } from "hardhat/config"
task("deploy:verifier", "Deploy a Verifier contract")
.addParam<number>("merkleTreeDepth", "Merkle tree depth", undefined, types.int)
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
.setAction(async ({ merkleTreeDepth, logs }, { ethers }): Promise<Contract> => {
const VerifierContractFactory = await ethers.getContractFactory(`Verifier${merkleTreeDepth}`)
const verifierContract = await VerifierContractFactory.deploy()
await verifierContract.deployed()
if (logs) {
console.info(`Verifier${merkleTreeDepth} contract has been deployed to: ${verifierContract.address}`)
}
return verifierContract
})

View File

@@ -40,7 +40,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/identity",
"version": "2.6.1",
"version": "3.2.3",
"description": "A library to create Semaphore identities.",
"license": "MIT",
"main": "dist/index.node.js",
@@ -30,6 +30,9 @@
"access": "public"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"poseidon-lite": "^0.1.0",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.31.2",
"typedoc": "^0.22.11"
@@ -39,6 +42,6 @@
"@ethersproject/keccak256": "^5.7.0",
"@ethersproject/random": "^5.5.1",
"@ethersproject/strings": "^5.6.1",
"poseidon-lite": "^0.0.2"
"js-sha512": "^0.8.0"
}
}

View File

@@ -1,6 +1,8 @@
import typescript from "rollup-plugin-typescript2"
import commonjs from "@rollup/plugin-commonjs"
import * as fs from "fs"
import cleanup from "rollup-plugin-cleanup"
import { nodeResolve } from "@rollup/plugin-node-resolve"
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
const banner = `/**
@@ -24,6 +26,8 @@ export default {
tsconfig: "./build.tsconfig.json",
useTsconfigDeclarationDir: true
}),
commonjs(),
nodeResolve(),
cleanup({ comments: "jsdoc" })
]
}

View File

@@ -1,11 +0,0 @@
import { keccak256 } from "@ethersproject/keccak256"
import { toUtf8Bytes } from "@ethersproject/strings"
/**
* Creates a keccak256 hash of a message compatible with the SNARK scalar modulus.
* @param message The message to be hashed.
* @returns The message digest.
*/
export default function hash(message: string): bigint {
return BigInt(keccak256(toUtf8Bytes(message))) >> BigInt(8)
}

View File

@@ -45,7 +45,7 @@ describe("Identity", () => {
it("Should not recreate an existing invalid identity", () => {
const fun = () => new Identity('[true, "01323"]')
expect(fun).toThrow("invalid BigNumber string")
expect(fun).toThrow("invalid BigNumber value")
})
it("Should recreate an existing identity", () => {
@@ -64,7 +64,9 @@ describe("Identity", () => {
const trapdoor = identity.getTrapdoor()
expect(trapdoor).toBe(BigInt("211007102311354422986775462856672883657031335757695461477990303178796954863"))
expect(trapdoor.toString()).toBe(
"11566083507498623434013707198824105161167204201250008419741119866456392774309"
)
})
})
@@ -74,7 +76,9 @@ describe("Identity", () => {
const nullifier = identity.getNullifier()
expect(nullifier).toBe(BigInt("10282208199720122340759039255952223220417076359839127631923809108800013776"))
expect(nullifier.toString()).toBe(
"14070056666392584007908120012103355272369511035580155843212703537125048345255"
)
})
})
@@ -82,8 +86,8 @@ describe("Identity", () => {
it("Should generate an identity commitment", () => {
const { commitment } = new Identity("message")
expect(commitment).toBe(
BigInt("13192222509545780880434144549342414064490325100975031303723930089730328393905")
expect(commitment.toString()).toBe(
"19361462367798001240039467285882167157718016385695743307694056771074972404368"
)
})
})
@@ -102,8 +106,8 @@ describe("Identity", () => {
const [trapdoor, nullifier] = JSON.parse(identity.toString())
expect(BigNumber.from(`0x${trapdoor}`).toBigInt()).toBe(identity.trapdoor)
expect(BigNumber.from(`0x${nullifier}`).toBigInt()).toBe(identity.nullifier)
expect(BigNumber.from(trapdoor).toBigInt()).toBe(identity.trapdoor)
expect(BigNumber.from(nullifier).toBigInt()).toBe(identity.nullifier)
})
})
})

View File

@@ -1,6 +1,6 @@
import { BigNumber } from "@ethersproject/bignumber"
import hash from "js-sha512"
import checkParameter from "./checkParameter"
import hash from "./hash"
import { generateCommitment, genRandomNumber, isJsonArray } from "./utils"
export default class Identity {
@@ -24,10 +24,10 @@ export default class Identity {
checkParameter(identityOrMessage, "identityOrMessage", "string")
if (!isJsonArray(identityOrMessage)) {
const messageHash = hash(identityOrMessage)
this._trapdoor = hash(`${messageHash}identity_trapdoor`)
this._nullifier = hash(`${messageHash}identity_nullifier`)
const h = hash.sha512(identityOrMessage).padStart(128, "0")
// alt_bn128 is 253.6 bits, so we can safely use 253 bits
this._trapdoor = BigInt(`0x${h.slice(64)}`) >> BigInt(3)
this._nullifier = BigInt(`0x${h.slice(0, 64)}`) >> BigInt(3)
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
return
@@ -35,8 +35,8 @@ export default class Identity {
const [trapdoor, nullifier] = JSON.parse(identityOrMessage)
this._trapdoor = BigNumber.from(`0x${trapdoor}`).toBigInt()
this._nullifier = BigNumber.from(`0x${nullifier}`).toBigInt()
this._trapdoor = BigNumber.from(trapdoor).toBigInt()
this._nullifier = BigNumber.from(nullifier).toBigInt()
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
}
@@ -94,6 +94,6 @@ export default class Identity {
* @returns The string representation of the identity.
*/
public toString(): string {
return JSON.stringify([this._trapdoor.toString(16), this._nullifier.toString(16)])
return JSON.stringify([`0x${this._trapdoor.toString(16)}`, `0x${this._nullifier.toString(16)}`])
}
}

View File

@@ -1,6 +1,7 @@
import { BigNumber } from "@ethersproject/bignumber"
import { randomBytes } from "@ethersproject/random"
import poseidon from "poseidon-lite"
import { poseidon1 } from "poseidon-lite/poseidon1"
import { poseidon2 } from "poseidon-lite/poseidon2"
/**
* Generates a random big number.
@@ -18,7 +19,7 @@ export function genRandomNumber(numberOfBytes = 31): bigint {
* @returns identity commitment
*/
export function generateCommitment(nullifier: bigint, trapdoor: bigint): bigint {
return poseidon([poseidon([nullifier, trapdoor])])
return poseidon1([poseidon2([nullifier, trapdoor])])
}
/**

View File

@@ -40,7 +40,7 @@
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>
@@ -73,12 +73,12 @@ yarn add @semaphore-protocol/identity @semaphore-protocol/group @semaphore-proto
import { Identity } from "@semaphore-protocol/identity"
import { Group } from "@semaphore-protocol/group"
import { generateProof } from "@semaphore-protocol/proof"
import { formatBytes32String } from "ethers/lib/utils"
import { utils } from "ethers"
const identity = new Identity()
const group = new Group()
const externalNullifier = 1
const signal = formatBytes32String("Hello world")
const externalNullifier = utils.formatBytes32String("Topic")
const signal = utils.formatBytes32String("Hello world")
group.addMembers([...identityCommitments, identity.generateCommitment()])
@@ -98,27 +98,3 @@ import { verifyProof } from "@semaphore-protocol/proof"
await verifyProof(fullProof, 20)
```
\# **packToSolidityProof**(proof: _Proof_): _SolidityProof_
```typescript
import { packToSolidityProof } from "@semaphore-protocol/proof"
const solidityProof = packToSolidityProof(fullProof.proof)
```
\# **generateNullifierHash**(externalNullifier: _BigNumberish_, identityNullifier: _BigNumberish_): _bigint_
```typescript
import { generateNullifierHash } from "@semaphore-protocol/proof"
const nullifierHash = generateNullifierHash(externalNullifier, identity.getNullifier())
```
\# **generateSignalHash**(signal: _string_): _bigint_
```typescript
import { generateSignalHash } from "@semaphore-protocol/proof"
const signalHash = generateSignalHash(signal)
```

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/proof",
"version": "2.6.1",
"version": "3.2.3",
"description": "A library to generate and verify Semaphore proofs.",
"license": "MIT",
"main": "dist/index.node.js",
@@ -37,8 +37,8 @@
"typedoc": "^0.22.11"
},
"peerDependencies": {
"@semaphore-protocol/group": "2.6.1",
"@semaphore-protocol/identity": "2.6.1"
"@semaphore-protocol/group": "3.2.3",
"@semaphore-protocol/identity": "3.2.3"
},
"dependencies": {
"@ethersproject/bignumber": "^5.5.0",
@@ -46,6 +46,6 @@
"@ethersproject/keccak256": "^5.7.0",
"@ethersproject/strings": "^5.5.0",
"@zk-kit/incremental-merkle-tree": "0.4.3",
"snarkjs": "^0.4.13"
"snarkjs": "0.4.13"
}
}

View File

@@ -1,11 +1,22 @@
import { BigNumber } from "@ethersproject/bignumber"
import { BytesLike, Hexable } from "@ethersproject/bytes"
import { Group } from "@semaphore-protocol/group"
import type { Identity } from "@semaphore-protocol/identity"
import { MerkleProof } from "@zk-kit/incremental-merkle-tree"
import { groth16 } from "snarkjs"
import hash from "./hash"
import packProof from "./packProof"
import { FullProof, SnarkArtifacts } from "./types"
/**
* Generates a Semaphore proof.
* @param identity The Semaphore identity.
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
* @param externalNullifier The external nullifier.
* @param signal The Semaphore signal.
* @param snarkArtifacts The SNARK artifacts.
* @returns The Semaphore proof ready to be verified.
*/
export default async function generateProof(
{ trapdoor, nullifier, commitment }: Identity,
groupOrMerkleProof: Group | MerkleProof,
@@ -48,12 +59,10 @@ export default async function generateProof(
)
return {
proof,
publicSignals: {
merkleTreeRoot: publicSignals[0],
nullifierHash: publicSignals[1],
signalHash: publicSignals[2],
externalNullifier: publicSignals[3]
}
merkleTreeRoot: publicSignals[0],
nullifierHash: publicSignals[1],
signal: BigNumber.from(signal).toString(),
externalNullifier: BigNumber.from(externalNullifier).toString(),
proof: packProof(proof)
}
}

View File

@@ -1,17 +1,19 @@
import { formatBytes32String } from "@ethersproject/strings"
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { getCurveFromName } from "ffjavascript"
import generateProof from "./generateProof"
import hash from "./hash"
import packToSolidityProof from "./packToSolidityProof"
import packProof from "./packProof"
import { FullProof } from "./types"
import unpackProof from "./unpackProof"
import verifyProof from "./verifyProof"
describe("Proof", () => {
const treeDepth = Number(process.env.TREE_DEPTH) || 20
const externalNullifier = 1
const signal = 2
const externalNullifier = formatBytes32String("Topic")
const signal = formatBytes32String("Hello world")
const wasmFilePath = `./snark-artifacts/${treeDepth}/semaphore.wasm`
const zkeyFilePath = `./snark-artifacts/${treeDepth}/semaphore.zkey`
@@ -65,7 +67,7 @@ describe("Proof", () => {
})
expect(typeof fullProof).toBe("object")
expect(fullProof.publicSignals.merkleTreeRoot).toBe(group.root.toString())
expect(fullProof.merkleTreeRoot).toBe(group.root.toString())
}, 20000)
it("Should generate a Semaphore proof passing a Merkle proof as parameter", async () => {
@@ -79,7 +81,7 @@ describe("Proof", () => {
})
expect(typeof fullProof).toBe("object")
expect(fullProof.publicSignals.merkleTreeRoot).toBe(group.root.toString())
expect(fullProof.merkleTreeRoot).toBe(group.root.toString())
}, 20000)
})
@@ -101,13 +103,17 @@ describe("Proof", () => {
it("Should hash the signal value correctly", async () => {
const signalHash = hash(signal)
expect(signalHash.toString()).toBe(fullProof.publicSignals.signalHash)
expect(signalHash.toString()).toBe(
"8665846418922331996225934941481656421248110469944536651334918563951783029"
)
})
it("Should hash the external nullifier value correctly", async () => {
const externalNullifierHash = hash(externalNullifier)
expect(externalNullifierHash.toString()).toBe(fullProof.publicSignals.externalNullifier)
expect(externalNullifierHash.toString()).toBe(
"244178201824278269437519042830883072613014992408751798420801126401127326826"
)
})
it("Should hash a number", async () => {
@@ -141,11 +147,12 @@ describe("Proof", () => {
})
})
describe("# packToSolidityProof", () => {
it("Should return a Solidity proof", async () => {
const solidityProof = packToSolidityProof(fullProof.proof)
describe("# packProof/unpackProof", () => {
it("Should return a packed proof", async () => {
const originalProof = unpackProof(fullProof.proof)
const proof = packProof(originalProof)
expect(solidityProof).toHaveLength(8)
expect(proof).toStrictEqual(fullProof.proof)
})
})
})

View File

@@ -1,6 +1,5 @@
import generateProof from "./generateProof"
import verifyProof from "./verifyProof"
import packToSolidityProof from "./packToSolidityProof"
export { generateProof, verifyProof, packToSolidityProof }
export { generateProof, verifyProof }
export * from "./types"

View File

@@ -0,0 +1,19 @@
import { SnarkJSProof, Proof } from "./types"
/**
* Packs a proof into a format compatible with Semaphore.
* @param originalProof The proof generated with SnarkJS.
* @returns The proof compatible with Semaphore.
*/
export default function packProof(originalProof: SnarkJSProof): Proof {
return [
originalProof.pi_a[0],
originalProof.pi_a[1],
originalProof.pi_b[0][1],
originalProof.pi_b[0][0],
originalProof.pi_b[1][1],
originalProof.pi_b[1][0],
originalProof.pi_c[0],
originalProof.pi_c[1]
]
}

View File

@@ -1,19 +0,0 @@
import { Proof, SolidityProof } from "./types"
/**
* Makes a proof compatible with the Verifier.sol method inputs.
* @param proof The proof generated with SnarkJS.
* @returns The Solidity compatible proof.
*/
export default function packToSolidityProof(proof: Proof): SolidityProof {
return [
proof.pi_a[0],
proof.pi_a[1],
proof.pi_b[0][1],
proof.pi_b[0][0],
proof.pi_b[1][1],
proof.pi_b[1][0],
proof.pi_c[0],
proof.pi_c[1]
]
}

View File

@@ -5,7 +5,7 @@ export type SnarkArtifacts = {
zkeyFilePath: string
}
export type Proof = {
export type SnarkJSProof = {
pi_a: BigNumberish[]
pi_b: BigNumberish[][]
pi_c: BigNumberish[]
@@ -14,18 +14,14 @@ export type Proof = {
}
export type FullProof = {
proof: Proof
publicSignals: PublicSignals
}
export type PublicSignals = {
merkleTreeRoot: BigNumberish
signal: BigNumberish
nullifierHash: BigNumberish
signalHash: BigNumberish
externalNullifier: BigNumberish
proof: Proof
}
export type SolidityProof = [
export type Proof = [
BigNumberish,
BigNumberish,
BigNumberish,

View File

@@ -0,0 +1,19 @@
import { SnarkJSProof, Proof } from "./types"
/**
* Unpacks a proof into its original form.
* @param proof The proof compatible with Semaphore.
* @returns The proof compatible with SnarkJS.
*/
export default function unpackProof(proof: Proof): SnarkJSProof {
return {
pi_a: [proof[0], proof[1]],
pi_b: [
[proof[3], proof[2]],
[proof[5], proof[4]]
],
pi_c: [proof[6], proof[7]],
protocol: "groth16",
curve: "bn128"
}
}

View File

@@ -1,14 +1,19 @@
import { groth16 } from "snarkjs"
import hash from "./hash"
import { FullProof } from "./types"
import unpackProof from "./unpackProof"
import verificationKeys from "./verificationKeys.json"
/**
* Verifies a SnarkJS proof.
* @param fullProof The SnarkJS full proof.
* Verifies a Semaphore proof.
* @param fullProof The SnarkJS Semaphore proof.
* @param treeDepth The Merkle tree depth.
* @returns True if the proof is valid, false otherwise.
*/
export default function verifyProof({ proof, publicSignals }: FullProof, treeDepth: number): Promise<boolean> {
export default function verifyProof(
{ merkleTreeRoot, nullifierHash, externalNullifier, signal, proof }: FullProof,
treeDepth: number
): Promise<boolean> {
if (treeDepth < 16 || treeDepth > 32) {
throw new TypeError("The tree depth must be a number between 16 and 32")
}
@@ -21,12 +26,7 @@ export default function verifyProof({ proof, publicSignals }: FullProof, treeDep
return groth16.verify(
verificationKey,
[
publicSignals.merkleTreeRoot,
publicSignals.nullifierHash,
publicSignals.signalHash,
publicSignals.externalNullifier
],
proof
[merkleTreeRoot, nullifierHash, hash(signal), hash(externalNullifier)],
unpackProof(proof)
)
}

View File

@@ -1,112 +0,0 @@
<p align="center">
<h1 align="center">
Semaphore subgraph
</h1>
<p align="center">A library to query Semaphore contracts.</p>
</p>
<p align="center">
<a href="https://github.com/semaphore-protocol">
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@semaphore-protocol/subgraph">
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/subgraph?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/subgraph.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>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
🤝 Code of conduct
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
🗣️ Chat &amp; Support
</a>
</h4>
</div>
| This library allows you to query the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/contracts/Semaphore.sol) contract data (i.e. groups) using the [Semaphore subgraph](https://github.com/semaphore-protocol/subgraph) on Kovan, Goerli, and Arbitrum One. It can be used on Node.js and browsers. |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## 🛠 Install
### npm or yarn
Install the `@semaphore-protocol/subgraph` package with npm:
```bash
npm i @semaphore-protocol/subgraph
```
or yarn:
```bash
yarn add @semaphore-protocol/subgraph
```
### CDN
You can also load it using a `script` tag using [unpkg](https://unpkg.com/):
```html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/@semaphore-protocol/subgraph/"></script>
```
or [JSDelivr](https://www.jsdelivr.com/):
```html
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@semaphore-protocol/subgraph/"></script>
```
## 📜 Usage
\# **new Subgraph**(network: _Network_ = "arbitrum" ): _Subgraph_
```typescript
import { Subgraph } from "@semaphore-protocol/subgraph"
const subgraph = new Subgraph()
```
\# **getGroups**(options?: _GroupOptions_)
```typescript
const groups = subgraph.getGroups()
// or
const groups = subgraph.getGroups({ members: true, verifiedProofs: true })
```
\# **getGroup**(groupId: _string_, options?: _GroupOptions_)
```typescript
const group = subgraph.getGroup("1")
// or
const { members, verifiedProofs } = subgraph.getGroup("1", { members: true, verifiedProofs: true })
```

View File

@@ -1,4 +0,0 @@
import Subgraph from "./subgraph"
export { Subgraph }
export * from "./types"

View File

@@ -1,6 +0,0 @@
export type Network = "goerli" | "arbitrum"
export type GroupOptions = {
members?: boolean
verifiedProofs?: boolean
}

17586
yarn.lock

File diff suppressed because it is too large Load Diff

1
yarn.lock.REMOVED.git-id Normal file
View File

@@ -0,0 +1 @@
5d109b5b737dadc4046717430e8fd16c727749b9