Compare commits

..

162 Commits

Author SHA1 Message Date
cedoor
b93acc884b chore: v3.5.0
Former-commit-id: 64a2e010f1
2023-03-31 13:14:48 +01:00
Cedoor
c9e565a445 Merge pull request #295 from semaphore-protocol/feat/identity-getter
New identity secret attribute and getter

Former-commit-id: 2af0af9a22
2023-03-31 12:53:30 +01:00
cedoor
6cea8c5dcc feat(identity): add new identity secret attribute and getter
re #294


Former-commit-id: 99ea3a15bc
2023-03-31 12:20:49 +01:00
Cedoor
5bd7cd93f7 Merge pull request #293 from semaphore-protocol/fix/semaphore-ethers
Correct nullifier hash parameter

Former-commit-id: 1118452cd4
2023-03-28 14:00:04 +01:00
cedoor
da1b4f6d8f fix(data): set correct nullifier hash parameter
Former-commit-id: d36a79e7ab
2023-03-28 12:52:46 +01:00
Cedoor
124f627d39 Merge pull request #292 from mmqxyz/patch-1
docs: fix small typos in Pairing.sol
Former-commit-id: 53963f7cd4
2023-03-28 11:01:50 +01:00
mmqxyz
199dca2a3b fix small typos in Pairing.sol
Former-commit-id: 9aa18b2fff
2023-03-28 11:32:29 +02:00
Cedoor
51accfc939 Merge pull request #291 from semaphore-protocol/docs/heyauthn-readme
Add new author

Former-commit-id: c77451857c
2023-03-24 10:16:47 +00:00
vplasencia
1696294881 style(heyauthn): format code with prettier
Former-commit-id: 220b34dade
2023-03-24 09:39:29 +01:00
vplasencia
c85b758120 docs(heyauthn): add new author
Former-commit-id: a31259011d
2023-03-24 09:28:38 +01:00
cedoor
12fd0f7a80 chore: v3.4.0
Former-commit-id: 254218b1e5
2023-03-21 19:41:10 +00:00
cedoor
78da99055d feat(cli): add support for arbitrum goerli
Former-commit-id: 7c4a2a1022
2023-03-21 19:40:40 +00:00
cedoor
77e4770b53 docs: add heyauthn docs link
Former-commit-id: ff692f1507
2023-03-21 19:35:50 +00:00
cedoor
799afc82f4 docs: add heyauthn package to readme
Former-commit-id: 79103bbbb0
2023-03-21 19:31:49 +00:00
cedoor
46fe2fc8f8 chore: v3.3.0
Former-commit-id: af89f3a0a1
2023-03-21 19:28:09 +00:00
cedoor
7bb9554388 style(heyauthn): format code with prettier
Former-commit-id: 587e3045c8
2023-03-21 19:22:22 +00:00
Cedoor
d88a71ec4d Merge pull request #285 from semaphore-protocol/feat/heyauthn
New HeyAuthn package

Former-commit-id: b497be753e
2023-03-21 19:17:11 +00:00
cedoor
2afc4ce1de style(cli): format code
Former-commit-id: 22ee1e0109
2023-03-21 11:36:42 +00:00
Cedoor
64f7b24c53 Merge pull request #281 from semaphore-protocol/feat/cli-sepolia-network
Add support for Sepolia network in the CLI

Former-commit-id: 889fe89162
2023-03-21 11:19:44 +00:00
Cedoor
88ba0af2d2 Merge pull request #282 from semaphore-protocol/chore/arbitrum-goerli
Semaphore support for Arbitrum Goerli network

Former-commit-id: 0a6061c9c5
2023-03-21 11:05:31 +00:00
vplasencia
c2e8ba6856 fix(cli): add admin in get-group when using SemaphoreEthers
Former-commit-id: 4641735932
2023-03-21 01:15:07 +01:00
cedoor
f9a8d68641 docs(heyauthn): add authors to readme file
re #284


Former-commit-id: 53637009b1
2023-03-20 16:37:55 +00:00
cedoor
df84100c22 feat(heyauthn): create heyauthn package
re #284


Former-commit-id: f67c1f07d7
2023-03-20 16:29:26 +00:00
cedoor
b962339203 feat(data): add support for arbitrum goerli network
re #275


Former-commit-id: 0571bbcdaa
2023-03-20 12:42:52 +00:00
Cedoor
073f5a5772 Merge pull request #283 from semaphore-protocol/feat/cli-template-hardhat
Add new version of cli-template-hardhat

Former-commit-id: e44ca7d447
2023-03-20 12:18:09 +00:00
vplasencia
b26f74a453 feat(cli-template-hardhat): add new version of cli-template-hardhat
Former-commit-id: 4133ae12c3
2023-03-20 12:36:12 +01:00
vplasencia
e9cac671f2 refactor(cli): remove unnecessary code in get-group command
Former-commit-id: 716a20cb7c
2023-03-17 23:11:40 +01:00
vplasencia
ecf8dcafb1 refactor(cli): remove unused code
Former-commit-id: 4ecf293cd4
2023-03-17 22:59:41 +01:00
vplasencia
f90c99193a refactor(cli): organize get-groups command code
Former-commit-id: eefffca9f4
2023-03-17 22:55:40 +01:00
vplasencia
a826708320 feat(cli): update get-group command to use sepolia
Former-commit-id: 4ddf75b378
2023-03-17 22:41:11 +01:00
cedoor
43370202a7 style(contracts): format code with prettier
Former-commit-id: ef393e9c03
2023-03-17 18:07:53 +00:00
cedoor
a4d1180d26 chore(contracts): deploy semaphore on arbitrum goerli network
re #275


Former-commit-id: e34ce9c61a
2023-03-17 18:01:40 +00:00
vplasencia
5c42f9e09c feat(cli): add support for sepolia network in the cli
Former-commit-id: 63666f6a9c
2023-03-17 14:27:58 +01:00
Cedoor
c8362e373b Merge pull request #280 from dbrans/patch-1
Comment typo in Pairing.sol

Former-commit-id: 032702b245
2023-03-17 10:53:27 +00:00
Derek Brans
06765f2f88 Fix typo in Pairing.sol
Former-commit-id: 0810ffbe0f
2023-03-16 19:15:42 -05:00
Cedoor
f26c84445e Merge pull request #279 from semaphore-protocol/chore/default-network
New default network (Sepolia)

Former-commit-id: 6c644522f0
2023-03-16 10:42:46 +00:00
cedoor
61a2d6adc2 test(data): update tests for semaphore ethers class
re #277


Former-commit-id: 8988f478c9
2023-03-15 19:04:23 +00:00
cedoor
eddd6b3dd5 chore(data): change default network
re #277


Former-commit-id: 49221b6ba7
2023-03-15 18:56:49 +00:00
cedoor
fa561f8f00 chore(cli): update command description
Former-commit-id: a4f9e47957
2023-03-15 18:55:34 +00:00
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
aguzmant103
f1aced7ee3 docs: adding GitPOAP link
Former-commit-id: f7e87c4442
2023-01-05 20:06:10 -06:00
101 changed files with 2931 additions and 18239 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>
@@ -155,23 +159,23 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
</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>
@@ -213,6 +217,28 @@ The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/heyauthn">
@semaphore-protocol/heyauthn
</a>
<a href="https://semaphore-protocol.github.io/semaphore/heyauthn">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/heyauthn.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/heyauthn.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tbody>
</table>

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

@@ -1,4 +1,4 @@
ETHEREUM_URL=
INFURA_API_KEY=
ETHEREUM_PRIVATE_KEY=
REPORT_GAS=false
COINMARKETCAP_API_KEY=

View File

@@ -0,0 +1,30 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
contract Feedback {
ISemaphore public semaphore;
uint256 public groupId;
constructor(address semaphoreAddress, uint256 _groupId) {
semaphore = ISemaphore(semaphoreAddress);
groupId = _groupId;
semaphore.createGroup(groupId, 20, address(this));
}
function joinGroup(uint256 identityCommitment) external {
semaphore.addMember(groupId, identityCommitment);
}
function sendFeedback(
uint256 feedback,
uint256 merkleTreeRoot,
uint256 nullifierHash,
uint256[8] calldata proof
) external {
semaphore.verifyProof(groupId, merkleTreeRoot, feedback, nullifierHash, groupId, proof);
}
}

View File

@@ -1,42 +0,0 @@
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.4;
import "@semaphore-protocol/contracts/interfaces/ISemaphore.sol";
/// @title Greeter contract.
/// @dev The following code is just a example to show how Semaphore can be used.
contract Greeter {
event NewGreeting(uint256 greeting);
event NewUser(uint256 identityCommitment, bytes32 username);
ISemaphore public semaphore;
uint256 groupId;
mapping(uint256 => bytes32) users;
constructor(address semaphoreAddress, uint256 _groupId) {
semaphore = ISemaphore(semaphoreAddress);
groupId = _groupId;
semaphore.createGroup(groupId, 20, address(this));
}
function joinGroup(uint256 identityCommitment, bytes32 username) external {
semaphore.addMember(groupId, identityCommitment);
users[identityCommitment] = username;
emit NewUser(identityCommitment, username);
}
function greet(
uint256 greeting,
uint256 merkleTreeRoot,
uint256 nullifierHash,
uint256[8] calldata proof
) external {
semaphore.verifyProof(groupId, merkleTreeRoot, greeting, nullifierHash, groupId, proof);
emit NewGreeting(greeting);
}
}

View File

@@ -1,35 +1,63 @@
import "@nomicfoundation/hardhat-toolbox"
import "@nomiclabs/hardhat-ethers"
import "@nomicfoundation/hardhat-chai-matchers"
import "@semaphore-protocol/hardhat"
import "@typechain/hardhat"
import { config as dotenvConfig } from "dotenv"
import "hardhat-gas-reporter"
import { HardhatUserConfig } from "hardhat/config"
import { NetworksUserConfig } from "hardhat/types"
import { resolve } from "path"
import "solidity-coverage"
import { config } from "./package.json"
import "./tasks/deploy"
dotenvConfig()
dotenvConfig({ path: resolve(__dirname, "../../.env") })
function getNetworks(): NetworksUserConfig {
if (process.env.ETHEREUM_URL && process.env.ETHEREUM_PRIVATE_KEY) {
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
return {
goerli: {
url: process.env.ETHEREUM_URL,
chainId: 5,
accounts
},
arbitrum: {
url: process.env.ETHEREUM_URL,
chainId: 42161,
accounts
}
}
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
return {}
}
return {}
const accounts = [`0x${process.env.ETHEREUM_PRIVATE_KEY}`]
const infuraApiKey = process.env.INFURA_API_KEY
return {
goerli: {
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
chainId: 5,
accounts
},
sepolia: {
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155111,
accounts
},
mumbai: {
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
chainId: 80001,
accounts
},
"optimism-goerli": {
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
chainId: 420,
accounts
},
arbitrum: {
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
chainId: 42161,
accounts
}
}
}
const config: HardhatUserConfig = {
solidity: "0.8.4",
const hardhatConfig: HardhatUserConfig = {
solidity: config.solidity,
paths: {
sources: config.paths.contracts,
tests: config.paths.tests,
cache: config.paths.cache,
artifacts: config.paths.build.contracts
},
networks: {
hardhat: {
chainId: 1337
@@ -40,7 +68,11 @@ const config: HardhatUserConfig = {
currency: "USD",
enabled: process.env.REPORT_GAS === "true",
coinmarketcap: process.env.COINMARKETCAP_API_KEY
},
typechain: {
outDir: config.paths.build.typechain,
target: "ethers-v5"
}
}
export default config
export default hardhatConfig

View File

@@ -1,12 +1,13 @@
{
"name": "@semaphore-protocol/cli-template-hardhat",
"version": "3.0.0-beta.7",
"version": "3.5.0",
"description": "Semaphore Hardhat template.",
"license": "Unlicense",
"files": [
".gitignore",
".env.example",
"contracts/",
"scripts/",
"tasks/",
"test/",
"hardhat.config.ts",
@@ -18,12 +19,14 @@
"access": "public"
},
"scripts": {
"start": "hardhat node",
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
"compile": "hardhat compile",
"deploy": "hardhat deploy",
"test": "hardhat test",
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
"deploy": "yarn compile && hardhat deploy",
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
"test:report-gas": "REPORT_GAS=true hardhat test",
"test:coverage": "hardhat coverage"
"test:coverage": "hardhat coverage",
"typechain": "hardhat typechain"
},
"devDependencies": {
"@ethersproject/abi": "^5.4.7",
@@ -33,10 +36,10 @@
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@semaphore-protocol/group": "3.0.0-beta.7",
"@semaphore-protocol/hardhat": "3.0.0-beta.7",
"@semaphore-protocol/identity": "3.0.0-beta.7",
"@semaphore-protocol/proof": "3.0.0-beta.7",
"@semaphore-protocol/group": "3.5.0",
"@semaphore-protocol/hardhat": "3.5.0",
"@semaphore-protocol/identity": "3.5.0",
"@semaphore-protocol/proof": "3.5.0",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@types/chai": "^4.2.0",
@@ -55,6 +58,21 @@
"typescript": ">=4.5.0"
},
"dependencies": {
"@semaphore-protocol/contracts": "3.0.0-beta.7"
"@semaphore-protocol/contracts": "3.5.0"
},
"config": {
"solidity": {
"version": "0.8.4"
},
"paths": {
"contracts": "./contracts",
"tests": "./test",
"cache": "./cache",
"build": {
"snark-artifacts": "./build/snark-artifacts",
"contracts": "./build/contracts",
"typechain": "./build/typechain"
}
}
}
}

View File

@@ -0,0 +1,24 @@
import download from "download"
import fs from "fs"
import { config } from "../package.json"
async function main() {
const snarkArtifactsPath = config.paths.build["snark-artifacts"]
const url = `http://www.trusted-setup-pse.org/semaphore/${20}`
if (!fs.existsSync(snarkArtifactsPath)) {
fs.mkdirSync(snarkArtifactsPath, { recursive: true })
}
if (!fs.existsSync(`${snarkArtifactsPath}/semaphore.zkey`)) {
await download(`${url}/semaphore.wasm`, snarkArtifactsPath)
await download(`${url}/semaphore.zkey`, snarkArtifactsPath)
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@@ -1,6 +1,6 @@
import { task, types } from "hardhat/config"
task("deploy", "Deploy a Greeter contract")
task("deploy", "Deploy a Feedback contract")
.addOptionalParam("semaphore", "Semaphore contract address", undefined, types.string)
.addOptionalParam("group", "Group id", "42", types.string)
.addOptionalParam("logs", "Print the logs", true, types.boolean)
@@ -13,15 +13,19 @@ task("deploy", "Deploy a Greeter contract")
semaphoreAddress = semaphore.address
}
const Greeter = await ethers.getContractFactory("Greeter")
const greeter = await Greeter.deploy(semaphoreAddress, groupId)
await greeter.deployed()
if (logs) {
console.info(`Greeter contract has been deployed to: ${greeter.address}`)
if (!groupId) {
groupId = process.env.GROUP_ID
}
return greeter
const FeedbackFactory = await ethers.getContractFactory("Feedback")
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
await feedbackContract.deployed()
if (logs) {
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
}
return feedbackContract
})

View File

@@ -0,0 +1,69 @@
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import { formatBytes32String } from "ethers/lib/utils"
import { run } from "hardhat"
// @ts-ignore: typechain folder will be generated after contracts compilation
import { Feedback } from "../build/typechain"
import { config } from "../package.json"
describe("Feedback", () => {
let feedbackContract: Feedback
let semaphoreContract: string
const groupId = "42"
const group = new Group(groupId)
const users: Identity[] = []
before(async () => {
const { semaphore } = await run("deploy:semaphore", {
logs: false
})
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address })
semaphoreContract = semaphore
users.push(new Identity())
users.push(new Identity())
})
describe("# joinGroup", () => {
it("Should allow users to join the group", async () => {
for await (const [i, user] of users.entries()) {
const transaction = feedbackContract.joinGroup(user.commitment)
group.addMember(user.commitment)
await expect(transaction)
.to.emit(semaphoreContract, "MemberAdded")
.withArgs(groupId, i, user.commitment, group.root)
}
})
})
describe("# sendFeedback", () => {
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
it("Should allow users to send feedback anonymously", async () => {
const feedback = formatBytes32String("Hello World")
const fullProof = await generateProof(users[1], group, groupId, feedback, {
wasmFilePath,
zkeyFilePath
})
const transaction = feedbackContract.sendFeedback(
feedback,
fullProof.merkleTreeRoot,
fullProof.nullifierHash,
fullProof.proof
)
await expect(transaction)
.to.emit(semaphoreContract, "ProofVerified")
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
})
})
})

View File

@@ -1,72 +0,0 @@
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof, packToSolidityProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import download from "download"
import { existsSync } from "fs"
import { ethers, run } from "hardhat"
import { Greeter } from "../typechain-types"
describe("Greeter", () => {
let greeter: Greeter
const snarkArtifactsURL = "https://www.trusted-setup-pse.org/semaphore/20"
const snarkArtifactsPath = "./artifacts/snark"
const users: any[] = []
const groupId = "42"
const group = new Group(groupId)
before(async () => {
if (!existsSync(`${snarkArtifactsPath}/semaphore.wasm`)) {
await download(`${snarkArtifactsURL}/semaphore.wasm`, `${snarkArtifactsPath}`)
await download(`${snarkArtifactsURL}/semaphore.zkey`, `${snarkArtifactsPath}`)
}
greeter = await run("deploy", { logs: false, group: groupId })
users.push({
identity: new Identity(),
username: ethers.utils.formatBytes32String("anon1")
})
users.push({
identity: new Identity(),
username: ethers.utils.formatBytes32String("anon2")
})
group.addMember(users[0].identity.commitment)
group.addMember(users[1].identity.commitment)
})
describe("# joinGroup", () => {
it("Should allow users to join the group", async () => {
for (let i = 0; i < group.members.length; i += 1) {
const transaction = greeter.joinGroup(group.members[i], users[i].username)
await expect(transaction).to.emit(greeter, "NewUser").withArgs(group.members[i], users[i].username)
}
})
})
describe("# greet", () => {
it("Should allow users to greet", async () => {
const greeting = ethers.utils.formatBytes32String("Hello World")
const fullProof = await generateProof(users[1].identity, group, groupId, greeting, {
wasmFilePath: `${snarkArtifactsPath}/semaphore.wasm`,
zkeyFilePath: `${snarkArtifactsPath}/semaphore.zkey`
})
const solidityProof = packToSolidityProof(fullProof.proof)
const transaction = greeter.greet(
greeting,
fullProof.publicSignals.merkleTreeRoot,
fullProof.publicSignals.nullifierHash,
solidityProof
)
await expect(transaction).to.emit(greeter, "NewGreeting").withArgs(greeting)
})
})
})

View File

@@ -1,12 +1,15 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "Node",
"noImplicitAny": true,
"resolveJsonModule": true,
"target": "ES2018",
"module": "CommonJS",
"strict": true,
"skipLibCheck": true
"esModuleInterop": true,
"outDir": "dist",
"typeRoots": ["node_modules/@types", "types"]
},
"include": ["./tasks", "./test", "./typechain-types"],
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
"files": ["./hardhat.config.ts"]
}

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

@@ -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": "3.0.0-beta.7",
"version": "3.5.0",
"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": "3.0.0-beta.7",
"@semaphore-protocol/data": "3.5.0",
"axios": "^1.3.2",
"boxen": "^7.0.1",
"chalk": "^5.1.2",
"commander": "^9.4.1",
"download": "^8.0.0",
"figlet": "^1.5.2",
"inquirer": "^9.1.4",
"log-symbols": "^5.1.0",
"ora": "^6.1.2"
"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, SemaphoreEthers, GroupResponse } from "@semaphore-protocol/data"
import chalk from "chalk"
import { program } from "commander"
import download from "download"
import figlet from "figlet"
import { existsSync, readFileSync, renameSync } from "fs"
import inquirer from "inquirer"
import logSymbols from "log-symbols"
import { dirname } from "path"
import { fileURLToPath } from "url"
import checkLatestVersion from "./checkLatestVersion.js"
import Spinner from "./spinner.js"
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
const { description, version } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"))
const supportedNetworks = ["sepolia", "goerli", "mumbai", "optimism-goerli", "arbitrum", "arbitrum-goerli"]
program
.name("semaphore")
.description(description)
@@ -29,27 +33,41 @@ program
program
.command("create")
.description("Create a Semaphore project with a supported template.")
.argument("<project-directory>", "Directory of the project.")
.argument("[project-directory]", "Directory of the project.")
// .option("-t, --template <template-name>", "Supported Semaphore template.", "hardhat")
.allowExcessArguments(false)
.action(async (projectDirectory) => {
if (!projectDirectory) {
const { projectName } = await inquirer.prompt({
name: "projectName",
type: "input",
message: "What is your project name?",
default: "my-app"
})
projectDirectory = projectName
}
const currentDirectory = process.cwd()
const spinner = new Spinner(`Creating your project in ${chalk.green(`./${projectDirectory}`)}`)
const templateURL = `https://registry.npmjs.org/@semaphore-protocol/cli-template-hardhat/-/cli-template-hardhat-${version}.tgz`
if (existsSync(projectDirectory)) {
console.info(`\n ${logSymbols.error}`, `error: the '${projectDirectory}' folder already exists\n`)
return
}
spinner.start()
await checkLatestVersion(version)
await download(templateURL, currentDirectory, { extract: true })
renameSync(`${currentDirectory}/package`, `${currentDirectory}/${projectDirectory}`)
spinner.stop()
console.info(` ${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`)
@@ -71,94 +89,216 @@ program
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")
.description("Get the list of groups from a supported network (e.g. goerli or arbitrum).")
.option("-n, --network <network-name>", "Supported Ethereum network.")
.allowExcessArguments(false)
.action(async ({ network }) => {
if (!["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)
let groupIds: string[]
const spinner = new Spinner("Fetching groups")
spinner.start()
try {
const groups = await subgraph.getGroups()
const semaphoreSubgraph = new SemaphoreSubgraph(network)
groupIds = await semaphoreSubgraph.getGroupIds()
spinner.stop()
} catch {
try {
const semaphoreEthers = new SemaphoreEthers(network)
groupIds = await semaphoreEthers.getGroupIds()
spinner.stop()
} catch {
spinner.stop()
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
if (groups.length === 0) {
console.info(` ${logSymbols.error}`, "error: there are no groups in this network\n")
return
}
const content = `${groups.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")
}
if (groupIds.length === 0) {
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
return
}
const content = `\n${groupIds.map((id: any) => ` - ${id}`).join("\n")}`
console.info(`${content}\n`)
})
program
.command("get-group")
.description("Get the data of a group from a supported network (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.")
.description("Get the data of a group from a supported network (e.g. goerli or arbitrum).")
.argument("[group-id]", "Identifier of the group.")
.option("-n, --network <network-name>", "Supported Ethereum network.")
.option("-m, --members", "Show group members.")
.option("-s, --signals", "Show group signals.")
.allowExcessArguments(false)
.action(async (groupId, { network, members, signals }) => {
if (!["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)
if (!groupId) {
let groupIds: string[]
const spinnerGroups = new Spinner("Fetching groups")
spinnerGroups.start()
try {
const semaphoreSubgraphGroups = new SemaphoreSubgraph(network)
groupIds = await semaphoreSubgraphGroups.getGroupIds()
spinnerGroups.stop()
} catch {
try {
const semaphoreEthersGroups = new SemaphoreEthers(network)
groupIds = await semaphoreEthersGroups.getGroupIds()
spinnerGroups.stop()
} catch {
spinnerGroups.stop()
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the SemaphoreEthers package")
return
}
}
if (groupIds.length === 0) {
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
return
}
const { selectedGroupId } = await inquirer.prompt({
name: "selectedGroupId",
type: "list",
message: "Select one of the following existing group ids:",
choices: groupIds
})
groupId = selectedGroupId
}
if (!members && !signals) {
const { showMembers } = await inquirer.prompt({
name: "showMembers",
type: "confirm",
message: "Do you want to show members?",
default: false
})
members = showMembers
const { showSignals } = await inquirer.prompt({
name: "showSignals",
type: "confirm",
message: "Do you want to show signals?",
default: false
})
signals = showSignals
}
let group: GroupResponse
const spinner = new Spinner(`Fetching group ${groupId}`)
spinner.start()
try {
const group = await subgraph.getGroup(groupId, { members, verifiedProofs: signals })
const semaphoreSubgraph = new SemaphoreSubgraph(network)
group = await semaphoreSubgraph.getGroup(groupId, { members, verifiedProofs: signals })
spinner.stop()
} catch {
try {
const semaphoreEthers = new SemaphoreEthers(network)
if (!group) {
console.info(` ${logSymbols.error}`, "error: the group does not exist\n")
group = await semaphoreEthers.getGroup(groupId)
if (members) {
group.members = await semaphoreEthers.getGroupMembers(groupId)
}
if (signals) {
group.verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId)
}
group.admin = await semaphoreEthers.getGroupAdmin(groupId)
spinner.stop()
} catch {
spinner.stop()
console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n")
return
}
let content = ` ${chalk.bold("Id")}: ${group.id}\n`
content += ` ${chalk.bold("Admin")}: ${group.admin}\n`
content += ` ${chalk.bold("Merkle tree")}:\n`
content += ` Root: ${group.merkleTree.root}\n`
content += ` Depth: ${group.merkleTree.depth}\n`
content += ` Zero value: ${group.merkleTree.zeroValue}\n`
content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`
if (members) {
content += `\n\n ${chalk.bold("Members")}: \n${group.members
.map((member: string, i: number) => ` ${i}. ${member}`)
.join("\n")}`
}
if (signals) {
content += `\n\n ${chalk.bold("Signals")}: \n${group.verifiedProofs
.map(({ signal }: any) => ` - ${signal}`)
.join("\n")}`
}
console.info(`${content}\n`)
} catch (error) {
spinner.stop()
console.info(` ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
}
let content = ` ${chalk.bold("Id")}: ${group.id}\n`
content += ` ${chalk.bold("Admin")}: ${group.admin}\n`
content += ` ${chalk.bold("Merkle tree")}:\n`
content += ` Root: ${group.merkleTree.root}\n`
content += ` Depth: ${group.merkleTree.depth}\n`
content += ` Zero value: ${group.merkleTree.zeroValue}\n`
content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`
if (members) {
content += `\n\n ${chalk.bold("Members")}: \n${group.members
.map((member: string, i: number) => ` ${i}. ${member}`)
.join("\n")}`
}
if (signals) {
content += `\n\n ${chalk.bold("Signals")}: \n${group.verifiedProofs
.map(({ signal }: any) => ` - ${signal}`)
.join("\n")}`
}
console.info(`\n${content}\n`)
})
program.parse()
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,12 +9,12 @@
pragma solidity ^0.8.4;
library Pairing {
error Semaphore__InvalidProof();
error InvalidProof();
// The prime q in the base field F_q for G1
uint256 constant BASE_MODULUS = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
// The prime moludus of the scalar field of G1.
// The prime modulus of the scalar field of G1.
uint256 constant SCALAR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
struct G1Point {
@@ -56,7 +56,7 @@ library Pairing {
// Validate input or revert
if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) {
revert Semaphore__InvalidProof();
revert InvalidProof();
}
// We know p.Y > 0 and p.Y < BASE_MODULUS.
@@ -82,17 +82,17 @@ library Pairing {
}
if (!success) {
revert Semaphore__InvalidProof();
revert InvalidProof();
}
}
/// @return r the product of a point on G1 and a scalar, i.e.
/// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
function scalar_mul(G1Point memory p, uint256 s) public view returns (G1Point memory r) {
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
// By EIP-196 the values p.X and p.Y are verified to be less than the BASE_MODULUS and
// form a valid point on the curve. But the scalar is not verified, so we do that explicitly.
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": "3.0.0-beta.7",
"version": "3.5.0",
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
"license": "MIT",
"files": [
"Semaphore.sol",
"**/*.sol"
],
"keywords": [

View File

@@ -0,0 +1,7 @@
{
"Pairing": "0xC0ae1a8D3505B2bE9DCe0e826abd722Afd13F1c9",
"SemaphoreVerifier": "0x346a936b19071b2f619200848B8ADbb938D72250",
"Poseidon": "0xb69aABB5D8d8e4920834761bD0C9DEEfa5D5502F",
"IncrementalBinaryTree": "0x9f44be9F69aF1e049dCeCDb2d9296f36C49Ceafb",
"Semaphore": "0xbE66454b0Fa9E6b3D53DC1b0f9D21978bb864531"
}

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

@@ -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,6 +30,26 @@ function getNetworks(): NetworksUserConfig {
chainId: 5,
accounts
},
sepolia: {
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155111,
accounts
},
mumbai: {
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
chainId: 80001,
accounts
},
"optimism-goerli": {
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
chainId: 420,
accounts
},
"arbitrum-goerli": {
url: "https://goerli-rollup.arbitrum.io/rpc",
chainId: 421613,
accounts
},
arbitrum: {
url: "https://arb1.arbitrum.io/rpc",
chainId: 42161,

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

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

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

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

View File

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

View File

@@ -4,6 +4,7 @@ import { HardhatConfig, HardhatUserConfig } from "hardhat/types"
import "hardhat-dependency-compiler"
import "@nomiclabs/hardhat-ethers"
import "./tasks/deploy-semaphore"
import "./tasks/deploy-semaphore-verifier"
extendConfig((config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
config.dependencyCompiler.paths = [

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
}
})

21
packages/heyauthn/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Vivek Bhupatiraju
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

119
packages/heyauthn/README.md Normal file
View File

@@ -0,0 +1,119 @@
<p align="center">
<h1 align="center">
HeyAuthn
</h1>
<p align="center">A library to allow developers to create and manage Semaphore identities using WebAuthn.</p>
</p>
<p align="center">
<a href="https://github.com/semaphore-protocol">
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@semaphore-protocol/heyauthn">
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/heyauthn?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/heyauthn.svg?style=flat-square" />
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
</a>
<a href="https://prettier.io/">
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
</a>
</p>
<div align="center">
<h4>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
👥 Contributing
</a>
<span>&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 developers to create and manage Semaphore identities using [WebAuthn](https://webauthn.io/) as a cross-device biometric authentication in a way that is more convenient, smoother and secure than localStorage, Chrome extensions, or password manager based solutions. |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## 🛠 Install
### npm or yarn
Install the `@semaphore-protocol/heyauthn` package with npm:
```bash
npm i @semaphore-protocol/heyauthn
```
or yarn:
```bash
yarn add @semaphore-protocol/heyauthn
```
## 📜 Usage
```typescript
import { HeyAuthn } from "@semaphore-protocol/heyauthn"
// STEP 1: Configure WebAuthn options.
const options = {
rpName: "my-app",
rpID: window.location.hostname,
userID: "my-id",
userName: "my-name"
}
// STEP 2: Register a new WebAuthn credential and get its Semaphore identity.
const { identity } = await HeyAuthn.fromRegister(options)
// Now you could also save the identity commitment in your DB (pseudocode).
fetch("/api/register" /* Replace this with your endpoint */, {
identity.commmitment
// ...
})
// STEP 3: Authenticate existing WebAuthn credential and signal.
const { identity } = await HeyAuthn.fromRegister(options)
// Get existing group and signal anonymously (pseudocode).
import { Group } from "@semaphore-protocol/group"
import { generateProof } from "@semaphore-protocol/proof"
import { utils } from "ethers"
const group = new Group("42")
group.addMembers(memberList)
const signal = utils.formatBytes32String("Hey anon!")
generateProof(identity, group, group.id, "42", {
zkeyFilePath: "./semaphore.zkey",
wasmFilePath: "./semaphore.wasm"
})
```
## Authors
- [Vivek Bhupatiraju](https://github.com/vb7401)
- [Richard Liu](https://github.com/rrrliu)
- [emma](https://github.com/emmaguo13)
- [Sehyun Chung](https://github.com/sehyunc)
- [Enrico Bottazzi](https://github.com/enricobottazzi)

View File

@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"declarationDir": "dist/types"
},
"include": ["src"]
}

View File

@@ -0,0 +1,42 @@
{
"name": "@semaphore-protocol/heyauthn",
"version": "3.5.0",
"description": "A library to allow developers to create and manage Semaphore identities using WebAuthn",
"license": "MIT",
"main": "dist/index.node.js",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.node.js"
},
"types": "dist/types/index.d.ts",
"files": [
"dist/",
"src/",
"LICENSE",
"README.md"
],
"repository": "https://github.com/semaphore-protocol/semaphore",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/heyauthn",
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
"scripts": {
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "yarn build",
"docs": "typedoc src/index.ts --out ../../docs/heyauthn"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.31.2",
"typedoc": "^0.22.11"
},
"dependencies": {
"@semaphore-protocol/identity": "3.5.0",
"@simplewebauthn/browser": "7.2.0",
"@simplewebauthn/server": "7.2.0"
}
}

View File

@@ -0,0 +1,29 @@
import typescript from "rollup-plugin-typescript2"
import * as fs from "fs"
import cleanup from "rollup-plugin-cleanup"
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf-8"))
const banner = `/**
* @module ${pkg.name}
* @version ${pkg.version}
* @file ${pkg.description}
* @copyright Vivek Bhupatiraju 2023
* @license ${pkg.license}
* @see [Github]{@link ${pkg.homepage}}
*/`
export default {
input: "src/index.ts",
output: [
{ file: pkg.exports.require, format: "cjs", banner, exports: "auto" },
{ file: pkg.exports.import, format: "es", banner }
],
external: Object.keys(pkg.dependencies),
plugins: [
typescript({
tsconfig: "./build.tsconfig.json",
useTsconfigDeclarationDir: true
}),
cleanup({ comments: "jsdoc" })
]
}

View File

@@ -0,0 +1,75 @@
import { Identity } from "@semaphore-protocol/identity"
import {
GenerateAuthenticationOptionsOpts as AuthenticationOptions,
GenerateRegistrationOptionsOpts as RegistrationOptions
} from "@simplewebauthn/server"
import HeyAuthn from "./heyAuthn"
jest.mock("@simplewebauthn/browser", () => ({
startRegistration: async () => ({
id: "my-new-credential",
rawId: "my-new-credential",
response: {
clientDataJSON: "",
attestationObject: ""
},
clientExtensionResults: {},
type: "public-key"
}),
startAuthentication: async () => ({
id: "my-existing-credential",
rawId: "my-existing-credential",
response: {
clientDataJSON: "",
attestationObject: ""
},
clientExtensionResults: {},
type: "public-key"
})
}))
describe("HeyAuthn", () => {
describe("# getIdentity", () => {
it("Should get the identity of the HeyAuthn instance", async () => {
const expectedIdentity = new Identity()
const heyAuthn = new HeyAuthn(expectedIdentity)
const identity = heyAuthn.getIdentity()
expect(expectedIdentity.toString()).toEqual(identity.toString())
})
})
describe("# fromRegister", () => {
const options: RegistrationOptions = {
rpName: "my-app",
rpID: "hostname",
userID: "my-id",
userName: "my-name"
}
it("Should create an identity identical to the one created registering credential", async () => {
const { identity } = await HeyAuthn.fromRegister(options)
const expectedIdentity = new Identity("my-new-credential")
expect(expectedIdentity.trapdoor).toEqual(identity.trapdoor)
expect(expectedIdentity.nullifier).toEqual(identity.nullifier)
expect(expectedIdentity.commitment).toEqual(identity.commitment)
})
})
describe("# fromAuthenticate", () => {
const options: AuthenticationOptions = {
rpID: "hostname"
}
it("Should create an identity identical to the one created authenticating credential", async () => {
const { identity } = await HeyAuthn.fromAuthenticate(options)
const expectedIdentity = new Identity("my-existing-credential")
expect(expectedIdentity.trapdoor).toEqual(identity.trapdoor)
expect(expectedIdentity.nullifier).toEqual(identity.nullifier)
expect(expectedIdentity.commitment).toEqual(identity.commitment)
})
})
})

View File

@@ -0,0 +1,62 @@
import {
generateAuthenticationOptions,
generateRegistrationOptions,
GenerateRegistrationOptionsOpts as RegistrationOptions,
GenerateAuthenticationOptionsOpts as AuthenticationOptions
} from "@simplewebauthn/server"
import { startAuthentication, startRegistration } from "@simplewebauthn/browser"
import { Identity } from "@semaphore-protocol/identity"
export default class HeyAuthn {
private _identity: Identity
constructor(identity: Identity) {
this._identity = identity
}
/**
* Registers a new WebAuthn credential and returns its HeyAuthn instance.
*
* @param {GenerateRegistrationOptionsOpts} options - WebAuthn options for registering a new credential.
* @returns A HeyAuthn instance with the newly registered credential.
*/
public static async fromRegister(options: RegistrationOptions) {
const registrationOptions = generateRegistrationOptions(options)
const { id } = await startRegistration(registrationOptions)
const identity = new Identity(id)
return new HeyAuthn(identity)
}
/**
* Authenticates an existing WebAuthn credential and returns its HeyAuthn instance.
*
* @param {GenerateAuthenticationOptionsOpts} options - WebAuthn options for authenticating an existing credential.
* @returns A HeyAuthn instance with the existing credential.
*/
public static async fromAuthenticate(options: AuthenticationOptions) {
const authenticationOptions = generateAuthenticationOptions(options)
const { id } = await startAuthentication(authenticationOptions)
const identity = new Identity(id)
return new HeyAuthn(identity)
}
/**
* Returns the Semaphore identity instance.
* @returns The Semaphore identity.
*/
public get identity(): Identity {
return this._identity
}
/**
* Returns the Semaphore identity instance.
* @returns The Semaphore identity.
*/
public getIdentity(): Identity {
return this._identity
}
}

View File

@@ -0,0 +1,10 @@
import { Identity } from "@semaphore-protocol/identity"
import { GenerateAuthenticationOptionsOpts, GenerateRegistrationOptionsOpts } from "@simplewebauthn/server"
import HeyAuthn from "./heyAuthn"
export {
HeyAuthn,
GenerateRegistrationOptionsOpts as RegistrationOptions,
GenerateAuthenticationOptionsOpts as AuthenticationOptions,
Identity
}

View File

@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "rollup.config.ts"]
}

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": "3.0.0-beta.7",
"version": "3.5.0",
"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

@@ -19,6 +19,7 @@ describe("Identity", () => {
expect(identity1.trapdoor).not.toBe(identity2.getTrapdoor())
expect(identity1.nullifier).not.toBe(identity2.getNullifier())
expect(identity1.secret).not.toBe(identity2.getSecret())
expect(identity1.commitment).not.toBe(identity2.getCommitment())
})
@@ -45,7 +46,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 +65,9 @@ describe("Identity", () => {
const trapdoor = identity.getTrapdoor()
expect(trapdoor).toBe(BigInt("211007102311354422986775462856672883657031335757695461477990303178796954863"))
expect(trapdoor.toString()).toBe(
"11566083507498623434013707198824105161167204201250008419741119866456392774309"
)
})
})
@@ -74,16 +77,28 @@ describe("Identity", () => {
const nullifier = identity.getNullifier()
expect(nullifier).toBe(BigInt("10282208199720122340759039255952223220417076359839127631923809108800013776"))
expect(nullifier.toString()).toBe(
"14070056666392584007908120012103355272369511035580155843212703537125048345255"
)
})
})
describe("# generateCommitment", () => {
it("Should generate an identity commitment", () => {
describe("# getSecret", () => {
it("Should return an identity secret", () => {
const { secret } = new Identity("message")
expect(secret.toString()).toBe(
"17452394798940441025978193762953691632066258438336130543532009665042636950194"
)
})
})
describe("# getCommitment", () => {
it("Should return an identity commitment", () => {
const { commitment } = new Identity("message")
expect(commitment).toBe(
BigInt("13192222509545780880434144549342414064490325100975031303723930089730328393905")
expect(commitment.toString()).toBe(
"19361462367798001240039467285882167157718016385695743307694056771074972404368"
)
})
})
@@ -102,8 +117,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,11 +1,14 @@
import { BigNumber } from "@ethersproject/bignumber"
import hash from "js-sha512"
import { poseidon1 } from "poseidon-lite/poseidon1"
import { poseidon2 } from "poseidon-lite/poseidon2"
import checkParameter from "./checkParameter"
import hash from "./hash"
import { generateCommitment, genRandomNumber, isJsonArray } from "./utils"
import { genRandomNumber, isJsonArray } from "./utils"
export default class Identity {
private _trapdoor: bigint
private _nullifier: bigint
private _secret: bigint
private _commitment: bigint
/**
@@ -16,7 +19,8 @@ export default class Identity {
if (identityOrMessage === undefined) {
this._trapdoor = genRandomNumber()
this._nullifier = genRandomNumber()
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
this._secret = poseidon2([this._nullifier, this._trapdoor])
this._commitment = poseidon1([this._secret])
return
}
@@ -24,20 +28,22 @@ 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`)
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
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._secret = poseidon2([this._nullifier, this._trapdoor])
this._commitment = poseidon1([this._secret])
return
}
const [trapdoor, nullifier] = JSON.parse(identityOrMessage)
this._trapdoor = BigNumber.from(`0x${trapdoor}`).toBigInt()
this._nullifier = BigNumber.from(`0x${nullifier}`).toBigInt()
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
this._trapdoor = BigNumber.from(trapdoor).toBigInt()
this._nullifier = BigNumber.from(nullifier).toBigInt()
this._secret = poseidon2([this._nullifier, this._trapdoor])
this._commitment = poseidon1([this._secret])
}
/**
@@ -72,6 +78,22 @@ export default class Identity {
return this._nullifier
}
/**
* Returns the identity secret.
* @returns The identity secret.
*/
public get secret(): bigint {
return this._secret
}
/**
* Returns the identity secret.
* @returns The identity secret.
*/
public getSecret(): bigint {
return this._secret
}
/**
* Returns the identity commitment.
* @returns The identity commitment.
@@ -94,6 +116,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,5 @@
import { BigNumber } from "@ethersproject/bignumber"
import { randomBytes } from "@ethersproject/random"
import poseidon from "poseidon-lite"
/**
* Generates a random big number.
@@ -11,16 +10,6 @@ export function genRandomNumber(numberOfBytes = 31): bigint {
return BigNumber.from(randomBytes(numberOfBytes)).toBigInt()
}
/**
* Generates the identity commitment from trapdoor and nullifier.
* @param nullifier The identity nullifier.
* @param trapdoor The identity trapdoor.
* @returns identity commitment
*/
export function generateCommitment(nullifier: bigint, trapdoor: bigint): bigint {
return poseidon([poseidon([nullifier, trapdoor])])
}
/**
* Checks if a string is a JSON.
* @param jsonString The JSON string.

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": "3.0.0-beta.7",
"version": "3.5.0",
"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": "3.0.0-beta.7",
"@semaphore-protocol/identity": "3.0.0-beta.7"
"@semaphore-protocol/group": "3.5.0",
"@semaphore-protocol/identity": "3.5.0"
},
"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 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_ = "goerli" ): _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
}

17555
yarn.lock

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More