mirror of
https://github.com/semaphore-protocol/semaphore.git
synced 2026-01-11 07:38:14 -05:00
Compare commits
147 Commits
v3.0.0-bet
...
v3.2.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1842eefc3 | ||
|
|
320187ff89 | ||
|
|
abbf1a1d30 | ||
|
|
59269b067d | ||
|
|
21bd9fe540 | ||
|
|
f60a4c02f1 | ||
|
|
d012310ae1 | ||
|
|
f01cb91472 | ||
|
|
31e6954aff | ||
|
|
7518d938d1 | ||
|
|
083d8a03a4 | ||
|
|
16c9e90ae4 | ||
|
|
1fe2745594 | ||
|
|
18d4b740ca | ||
|
|
8392173370 | ||
|
|
26490304e4 | ||
|
|
1878ce2320 | ||
|
|
98aed04bd9 | ||
|
|
5a0585cec2 | ||
|
|
1a51263e5c | ||
|
|
4d7a976f06 | ||
|
|
fe07103ff2 | ||
|
|
aa230345fb | ||
|
|
d5c24c0f9c | ||
|
|
c1752dcb2e | ||
|
|
b00ff99e5e | ||
|
|
00e3c4a7f5 | ||
|
|
0176a67e3a | ||
|
|
4fa9f41483 | ||
|
|
64b9ef28e8 | ||
|
|
d7ce540f88 | ||
|
|
3411f55403 | ||
|
|
1bc492c39d | ||
|
|
6197a03734 | ||
|
|
6369fdfa71 | ||
|
|
48f779cd90 | ||
|
|
3ff6709a7b | ||
|
|
90bb4ccfa0 | ||
|
|
5f29690641 | ||
|
|
2665a440fa | ||
|
|
cf31913d27 | ||
|
|
103a676455 | ||
|
|
9aa5bc5840 | ||
|
|
894bc87952 | ||
|
|
cd4bd6ad91 | ||
|
|
dca7b67677 | ||
|
|
60d698f6ee | ||
|
|
f05e654f95 | ||
|
|
9a4fe6ba64 | ||
|
|
c8d472a286 | ||
|
|
c9711b9007 | ||
|
|
8afcac50c2 | ||
|
|
75c1c1373c | ||
|
|
2318628ca2 | ||
|
|
2603600716 | ||
|
|
3f0f1e006c | ||
|
|
2a4f2455d7 | ||
|
|
0ea221d9ea | ||
|
|
437674f3bc | ||
|
|
4202186c0b | ||
|
|
383e818833 | ||
|
|
74014a42b5 | ||
|
|
e875fa4aa3 | ||
|
|
186dae79d2 | ||
|
|
195d545a95 | ||
|
|
84245b9b1f | ||
|
|
461e91f53b | ||
|
|
2893feca3c | ||
|
|
4be2973761 | ||
|
|
01a30fd8af | ||
|
|
a70c1b001c | ||
|
|
30ed65d569 | ||
|
|
04a6649f2f | ||
|
|
a337021d8d | ||
|
|
4cd3863d03 | ||
|
|
c88639659d | ||
|
|
0ca17d078b | ||
|
|
f171506891 | ||
|
|
e085ecd0df | ||
|
|
b744ee1e7b | ||
|
|
8d51a6689d | ||
|
|
7bec0a4e42 | ||
|
|
71ed4d605d | ||
|
|
810a5358c0 | ||
|
|
636a9156b4 | ||
|
|
a679d047fa | ||
|
|
6d3e685284 | ||
|
|
3bfcf67b9d | ||
|
|
e16dbe663e | ||
|
|
0aca50ea34 | ||
|
|
54a6abdd2b | ||
|
|
de32db6d2d | ||
|
|
d43f9278e0 | ||
|
|
f3d8c4ee75 | ||
|
|
60fedc4e90 | ||
|
|
34d5743fa4 | ||
|
|
6810fcb5bb | ||
|
|
0bdf064614 | ||
|
|
7c4d2c2007 | ||
|
|
1bb57ca607 | ||
|
|
44860d70b9 | ||
|
|
d286a8f1e8 | ||
|
|
729ba41afd | ||
|
|
972fde32a7 | ||
|
|
c3d9e23e2a | ||
|
|
5fd7435c6d | ||
|
|
07888687a0 | ||
|
|
54c3a54597 | ||
|
|
81dba510c3 | ||
|
|
343cdabe9c | ||
|
|
fe6fbc1a0d | ||
|
|
f854492709 | ||
|
|
30af236547 | ||
|
|
d65b4aae47 | ||
|
|
245fb2cba8 | ||
|
|
f469ccc36a | ||
|
|
8ee1bd9c1b | ||
|
|
348a4327d7 | ||
|
|
faffde79f0 | ||
|
|
89a83c7b6b | ||
|
|
f36e8c1536 | ||
|
|
46ed508de8 | ||
|
|
821cdb7fd3 | ||
|
|
dd2df7ab06 | ||
|
|
71fdabe65c | ||
|
|
cce6c31461 | ||
|
|
f1aced7ee3 | ||
|
|
bd8b06bb7b | ||
|
|
9f692e57e8 | ||
|
|
32aabf81d2 | ||
|
|
a72a57ef63 | ||
|
|
4e5b4495a3 | ||
|
|
578d426d1a | ||
|
|
362b025fac | ||
|
|
84aa8d0eac | ||
|
|
6592a62f25 | ||
|
|
a59a2af50b | ||
|
|
1e68ed1fec | ||
|
|
2abd068cf8 | ||
|
|
8b746ff2bc | ||
|
|
23d9786e16 | ||
|
|
3b20402476 | ||
|
|
91bcc66c17 | ||
|
|
bcf9dad496 | ||
|
|
29195519fb | ||
|
|
17efdb38af | ||
|
|
8713e2b339 |
14
.github/workflows/auto-assign.yml
vendored
Normal file
14
.github/workflows/auto-assign.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: auto-assign
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: wow-actions/auto-assign@v3
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
reviewers: org/core-devs
|
||||
43
.github/workflows/release.yml
vendored
Normal file
43
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: release
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
|
||||
|
||||
- name: Restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- run: yarn version:release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
4
.husky/commit-msg
Executable file
4
.husky/commit-msg
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit $1
|
||||
@@ -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
|
||||
|
||||
|
||||
23
.yarn/patches/changelogithub-npm-0.12.7-72f348805d.patch
Normal file
23
.yarn/patches/changelogithub-npm-0.12.7-72f348805d.patch
Normal file
@@ -0,0 +1,23 @@
|
||||
diff --git a/dist/shared/changelogithub.821fab93.mjs b/dist/shared/changelogithub.821fab93.mjs
|
||||
index 5fc2100867613c20f7827eac8715a5fc28bdc39e..97bd8dff878b81c63d2220e496904f6f3933589a 100644
|
||||
--- a/dist/shared/changelogithub.821fab93.mjs
|
||||
+++ b/dist/shared/changelogithub.821fab93.mjs
|
||||
@@ -181,7 +181,7 @@ function formatLine(commit, options) {
|
||||
function formatTitle(name, options) {
|
||||
if (!options.emoji)
|
||||
name = name.replace(emojisRE, "");
|
||||
- return `### ${name.trim()}`;
|
||||
+ return `## ${name.trim()}`;
|
||||
}
|
||||
function formatSection(commits, sectionName, options) {
|
||||
if (!commits.length)
|
||||
@@ -198,7 +198,8 @@ function formatSection(commits, sectionName, options) {
|
||||
Object.keys(scopes).sort().forEach((scope) => {
|
||||
let padding = "";
|
||||
let prefix = "";
|
||||
- const scopeText = `**${options.scopeMap[scope] || scope}**`;
|
||||
+ const url = `https://github.com/${options.github}/tree/main/packages/${scope}`
|
||||
+ const scopeText = `[**@${options.github.split("/")[0]}/${options.scopeMap[scope] || scope}**](${url})`
|
||||
if (scope && useScopeGroup) {
|
||||
lines.push(`- ${scopeText}:`);
|
||||
padding = " ";
|
||||
1
.yarn/plugins/@yarnpkg/plugin-version.cjs.REMOVED.git-id
vendored
Normal file
1
.yarn/plugins/@yarnpkg/plugin-version.cjs.REMOVED.git-id
vendored
Normal file
@@ -0,0 +1 @@
|
||||
87de4f440a77841135f97a187e09140c6d4e6ae2
|
||||
@@ -1,8 +1,11 @@
|
||||
nodeLinker: node-modules
|
||||
checksumBehavior: update
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
|
||||
spec: "@yarnpkg/plugin-version"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.1.cjs
|
||||
|
||||
24
README.md
24
README.md
@@ -32,6 +32,10 @@
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier">
|
||||
</a>
|
||||
<img alt="Repository top language" src="https://img.shields.io/github/languages/top/semaphore-protocol/semaphore?style=flat-square">
|
||||
<a href="https://www.gitpoap.io/gh/semaphore-protocol/semaphore" target="_blank">
|
||||
<img src="https://public-api.gitpoap.io/v1/repo/semaphore-protocol/semaphore/badge">
|
||||
</a>
|
||||
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
@@ -48,7 +52,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
@@ -57,9 +61,7 @@
|
||||
| Semaphore is a protocol, designed to be a simple and generic privacy layer for Ethereum DApps. Using zero knowledge, Ethereum users can prove their membership of a group and send signals such as votes or endorsements without revealing their original identity. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) (NPM: `@semaphore-protocol/contracts`) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
|
||||
|
||||
You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-protocol/semaphore/tree/version/1.0.0).
|
||||
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
|
||||
|
||||
---
|
||||
|
||||
@@ -157,23 +159,23 @@ You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-prot
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/packages/subgraph">
|
||||
@semaphore-protocol/subgraph
|
||||
<a href="/packages/data">
|
||||
@semaphore-protocol/data
|
||||
</a>
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/subgraph">
|
||||
<a href="https://semaphore-protocol.github.io/semaphore/data">
|
||||
(docs)
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- NPM version -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/subgraph.svg?style=flat-square" alt="NPM version" />
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/data">
|
||||
<img src="https://img.shields.io/npm/v/@semaphore-protocol/data.svg?style=flat-square" alt="NPM version" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<!-- Downloads -->
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/subgraph.svg?style=flat-square" alt="Downloads" />
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/data">
|
||||
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/data.svg?style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
7
changelogithub.config.json
Normal file
7
changelogithub.config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"types": {
|
||||
"feat": { "title": "🚀 Features" },
|
||||
"fix": { "title": "🐞 Bug Fixes" },
|
||||
"refactor": { "title": "♻️ Refactoring" }
|
||||
}
|
||||
}
|
||||
15
package.json
15
package.json
@@ -7,7 +7,7 @@
|
||||
"bugs": "https://github.com/semaphore-protocol/semaphore/issues",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:libraries": "yarn workspaces foreach run build",
|
||||
"build:libraries": "yarn workspaces foreach -t run build",
|
||||
"compile:contracts": "yarn workspace contracts compile",
|
||||
"download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts",
|
||||
"test": "yarn test:libraries && yarn test:contracts",
|
||||
@@ -17,9 +17,12 @@
|
||||
"prettier": "prettier -c .",
|
||||
"prettier:write": "prettier -w .",
|
||||
"docs": "yarn workspaces foreach run docs",
|
||||
"version:bump": "yarn workspaces foreach --no-private version -d ${0} && yarn version apply --all && git commit -am \"chore: v${0}\" && git tag v${0}",
|
||||
"version:publish": "yarn build:libraries && yarn workspaces foreach --no-private npm publish --tolerate-republish",
|
||||
"version:release": "changelogithub",
|
||||
"commit": "cz",
|
||||
"precommit": "lint-staged",
|
||||
"postinstall": "yarn download:snark-artifacts"
|
||||
"postinstall": "yarn download:snark-artifacts && husky install"
|
||||
},
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
@@ -35,7 +38,8 @@
|
||||
"monorepo"
|
||||
],
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
"packages/*",
|
||||
"packages/contracts/contracts"
|
||||
],
|
||||
"packageManager": "yarn@3.2.1",
|
||||
"devDependencies": {
|
||||
@@ -53,6 +57,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"babel-jest": "^27.4.6",
|
||||
"changelogithub": "0.12.7",
|
||||
"commitizen": "^4.2.4",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"dotenv": "^16.0.2",
|
||||
@@ -62,6 +67,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.2",
|
||||
"eslint-plugin-jest": "^25.7.0",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^27.4.1",
|
||||
"jest-config": "^27.4.7",
|
||||
"lint-staged": "^12.1.7",
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
10
packages/cli-template-hardhat/.gitignore
vendored
Normal file
10
packages/cli-template-hardhat/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
node_modules
|
||||
.env
|
||||
coverage
|
||||
coverage.json
|
||||
typechain
|
||||
typechain-types
|
||||
|
||||
# Hardhat files
|
||||
cache
|
||||
artifacts
|
||||
@@ -18,7 +18,7 @@ contract Greeter {
|
||||
semaphore = ISemaphore(semaphoreAddress);
|
||||
groupId = _groupId;
|
||||
|
||||
semaphore.createGroup(groupId, 20, 0, address(this));
|
||||
semaphore.createGroup(groupId, 20, address(this));
|
||||
}
|
||||
|
||||
function joinGroup(uint256 identityCommitment, bytes32 username) external {
|
||||
@@ -35,7 +35,7 @@ contract Greeter {
|
||||
uint256 nullifierHash,
|
||||
uint256[8] calldata proof
|
||||
) external {
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, greeting, nullifierHash, groupId, proof);
|
||||
semaphore.verifyProof(groupId, merkleTreeRoot, uint256(greeting), nullifierHash, groupId, proof);
|
||||
|
||||
emit NewGreeting(greeting);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,26 @@ function getNetworks(): NetworksUserConfig {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: process.env.ETHEREUM_URL,
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/cli-template-hardhat",
|
||||
"version": "0.4.0",
|
||||
"version": "3.2.3",
|
||||
"description": "Semaphore Hardhat template.",
|
||||
"license": "Unlicense",
|
||||
"files": [
|
||||
".gitignore",
|
||||
".env.example",
|
||||
"contracts/",
|
||||
"tasks/",
|
||||
"test/",
|
||||
@@ -31,10 +33,10 @@
|
||||
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
|
||||
"@nomiclabs/hardhat-ethers": "^2.0.0",
|
||||
"@nomiclabs/hardhat-etherscan": "^3.0.0",
|
||||
"@semaphore-protocol/group": "2.6.1",
|
||||
"@semaphore-protocol/hardhat": "^0.1.0",
|
||||
"@semaphore-protocol/identity": "2.6.1",
|
||||
"@semaphore-protocol/proof": "2.6.1",
|
||||
"@semaphore-protocol/group": "3.2.3",
|
||||
"@semaphore-protocol/hardhat": "3.2.3",
|
||||
"@semaphore-protocol/identity": "3.2.3",
|
||||
"@semaphore-protocol/proof": "3.2.3",
|
||||
"@typechain/ethers-v5": "^10.1.0",
|
||||
"@typechain/hardhat": "^6.1.2",
|
||||
"@types/chai": "^4.2.0",
|
||||
@@ -53,6 +55,6 @@
|
||||
"typescript": ">=4.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/contracts": "2.6.1"
|
||||
"@semaphore-protocol/contracts": "3.2.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,11 @@ task("deploy", "Deploy a Greeter contract")
|
||||
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, semaphore: semaphoreAddress, group: groupId }, { ethers, run }) => {
|
||||
if (!semaphoreAddress) {
|
||||
const { address: verifierAddress } = await run("deploy:verifier", { logs, merkleTreeDepth: 20 })
|
||||
|
||||
const { address } = await run("deploy:semaphore", {
|
||||
logs,
|
||||
verifiers: [
|
||||
{
|
||||
merkleTreeDepth: 20,
|
||||
contractAddress: verifierAddress
|
||||
}
|
||||
]
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs
|
||||
})
|
||||
|
||||
semaphoreAddress = address
|
||||
semaphoreAddress = semaphore.address
|
||||
}
|
||||
|
||||
const Greeter = await ethers.getContractFactory("Greeter")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Group } from "@semaphore-protocol/group"
|
||||
import { Identity } from "@semaphore-protocol/identity"
|
||||
import { generateProof, packToSolidityProof } from "@semaphore-protocol/proof"
|
||||
import { generateProof } from "@semaphore-protocol/proof"
|
||||
import { expect } from "chai"
|
||||
import download from "download"
|
||||
import { existsSync } from "fs"
|
||||
import { ethers, run } from "hardhat"
|
||||
// @ts-ignore: typechain-types folder will be generated after contracts compilation
|
||||
import { Greeter } from "../typechain-types"
|
||||
|
||||
describe("Greeter", () => {
|
||||
@@ -15,7 +16,7 @@ describe("Greeter", () => {
|
||||
|
||||
const users: any[] = []
|
||||
const groupId = "42"
|
||||
const group = new Group()
|
||||
const group = new Group(groupId)
|
||||
|
||||
before(async () => {
|
||||
if (!existsSync(`${snarkArtifactsPath}/semaphore.wasm`)) {
|
||||
@@ -35,8 +36,8 @@ describe("Greeter", () => {
|
||||
username: ethers.utils.formatBytes32String("anon2")
|
||||
})
|
||||
|
||||
group.addMember(users[0].identity.generateCommitment())
|
||||
group.addMember(users[1].identity.generateCommitment())
|
||||
group.addMember(users[0].identity.commitment)
|
||||
group.addMember(users[1].identity.commitment)
|
||||
})
|
||||
|
||||
describe("# joinGroup", () => {
|
||||
@@ -57,13 +58,12 @@ describe("Greeter", () => {
|
||||
wasmFilePath: `${snarkArtifactsPath}/semaphore.wasm`,
|
||||
zkeyFilePath: `${snarkArtifactsPath}/semaphore.zkey`
|
||||
})
|
||||
const solidityProof = packToSolidityProof(fullProof.proof)
|
||||
|
||||
const transaction = greeter.greet(
|
||||
greeting,
|
||||
fullProof.publicSignals.merkleRoot,
|
||||
fullProof.publicSignals.nullifierHash,
|
||||
solidityProof
|
||||
fullProof.merkleTreeRoot,
|
||||
fullProof.nullifierHash,
|
||||
fullProof.proof
|
||||
)
|
||||
|
||||
await expect(transaction).to.emit(greeter, "NewGreeting").withArgs(greeting)
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
@@ -60,7 +60,7 @@ npm i -g @semaphore-protocol/cli
|
||||
or run specific commands with `npx`:
|
||||
|
||||
```bash
|
||||
npx @semaphore-protocol/cli init my-app
|
||||
npx @semaphore-protocol/cli create my-app
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
@@ -75,7 +75,7 @@ Options:
|
||||
-h, --help Display this help.
|
||||
|
||||
Commands:
|
||||
init <project-directory> Initialize a Semaphore project with a supported template.
|
||||
create <project-directory> Create a Semaphore project with a supported template.
|
||||
get-groups [options] Get the list of groups from a supported network (Goerli or Arbitrum).
|
||||
get-group [options] <group-id> Get the data of a group from a supported network (Goerli or Arbitrum).
|
||||
help [command] Display help for a specific command.
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
"baseUrl": ".",
|
||||
"target": "es2020",
|
||||
"module": "ESNext",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"paths": {
|
||||
"@semaphore-protocol/subgraph": ["../subgraph/src/index.ts"]
|
||||
}
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/cli",
|
||||
"type": "module",
|
||||
"version": "0.4.0",
|
||||
"version": "3.2.3",
|
||||
"description": "A command line tool to set up your Semaphore project and get group data.",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -34,18 +34,23 @@
|
||||
"devDependencies": {
|
||||
"@types/clear": "^0.1.2",
|
||||
"@types/figlet": "^1.5.5",
|
||||
"@types/semver": "^7.3.13",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@semaphore-protocol/subgraph": "2.6.1",
|
||||
"@semaphore-protocol/data": "3.2.3",
|
||||
"axios": "^1.3.2",
|
||||
"boxen": "^7.0.1",
|
||||
"chalk": "^5.1.2",
|
||||
"commander": "^9.4.1",
|
||||
"download": "^8.0.0",
|
||||
"figlet": "^1.5.2",
|
||||
"inquirer": "^9.1.4",
|
||||
"log-symbols": "^5.1.0",
|
||||
"ora": "^6.1.2"
|
||||
"ora": "^6.1.2",
|
||||
"semver": "^7.3.8"
|
||||
}
|
||||
}
|
||||
|
||||
48
packages/cli/src/checkLatestVersion.ts
Normal file
48
packages/cli/src/checkLatestVersion.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import axios from "axios"
|
||||
import boxen from "boxen"
|
||||
import chalk from "chalk"
|
||||
import { execSync } from "child_process"
|
||||
import { lt as semverLt } from "semver"
|
||||
|
||||
const cliRegistryURL = "https://registry.npmjs.org/-/package/@semaphore-protocol/cli/dist-tags"
|
||||
|
||||
/**
|
||||
* Checks the registry directly via the API, if that fails, tries the slower `npm view [package] version` command.
|
||||
* This is important for users in environments where direct access to npm is blocked by a firewall, and packages are
|
||||
* provided exclusively via a private registry.
|
||||
* @param currentVersion The current version of the CLI.
|
||||
*/
|
||||
export default async function checkLatestVersion(currentVersion: string) {
|
||||
let latestVersion: string
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(cliRegistryURL)
|
||||
|
||||
latestVersion = data.latest
|
||||
} catch {
|
||||
try {
|
||||
latestVersion = execSync("npm view @semaphore-protocol/cli version").toString().trim()
|
||||
} catch {
|
||||
latestVersion = null
|
||||
}
|
||||
}
|
||||
|
||||
if (latestVersion && semverLt(currentVersion, latestVersion)) {
|
||||
console.info("\n")
|
||||
console.info(
|
||||
boxen(
|
||||
chalk.white(
|
||||
`Update available ${chalk.gray(currentVersion)} -> ${chalk.green(
|
||||
latestVersion
|
||||
)} \n\n You are currently using @semaphore-protocol/cli ${chalk.gray(
|
||||
currentVersion
|
||||
)} which is behind the latest release ${chalk.green(latestVersion)} \n\n Run ${chalk.cyan(
|
||||
"npm install -g @semaphore-protocol/cli@latest"
|
||||
)} to get the latest version`
|
||||
),
|
||||
{ padding: 1, borderColor: "yellow", textAlignment: "center" }
|
||||
)
|
||||
)
|
||||
console.info("")
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,21 @@
|
||||
import { Subgraph } from "@semaphore-protocol/subgraph"
|
||||
import { SemaphoreSubgraph } from "@semaphore-protocol/data"
|
||||
import chalk from "chalk"
|
||||
import { program } from "commander"
|
||||
import download from "download"
|
||||
import figlet from "figlet"
|
||||
import { existsSync, readFileSync, renameSync } from "fs"
|
||||
import inquirer from "inquirer"
|
||||
import logSymbols from "log-symbols"
|
||||
import { dirname } from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import checkLatestVersion from "./checkLatestVersion.js"
|
||||
import Spinner from "./spinner.js"
|
||||
|
||||
const packagePath = `${dirname(fileURLToPath(import.meta.url))}/..`
|
||||
const { description, version } = JSON.parse(readFileSync(`${packagePath}/package.json`, "utf8"))
|
||||
|
||||
const supportedNetworks = ["goerli", "mumbai", "optimism-goerli", "arbitrum"]
|
||||
|
||||
program
|
||||
.name("semaphore")
|
||||
.description(description)
|
||||
@@ -27,11 +31,22 @@ program
|
||||
})
|
||||
|
||||
program
|
||||
.command("init")
|
||||
.description("Initialize a Semaphore project with a supported template.")
|
||||
.argument("<project-directory>", "Directory of the project.")
|
||||
.command("create")
|
||||
.description("Create a Semaphore project with a supported template.")
|
||||
.argument("[project-directory]", "Directory of the project.")
|
||||
// .option("-t, --template <template-name>", "Supported Semaphore template.", "hardhat")
|
||||
.allowExcessArguments(false)
|
||||
.action(async (projectDirectory) => {
|
||||
if (!projectDirectory) {
|
||||
const { projectName } = await inquirer.prompt({
|
||||
name: "projectName",
|
||||
type: "input",
|
||||
message: "What is your project name?",
|
||||
default: "my-app"
|
||||
})
|
||||
projectDirectory = projectName
|
||||
}
|
||||
|
||||
const currentDirectory = process.cwd()
|
||||
const spinner = new Spinner(`Creating your project in ${chalk.green(`./${projectDirectory}`)}`)
|
||||
const templateURL = `https://registry.npmjs.org/@semaphore-protocol/cli-template-hardhat/-/cli-template-hardhat-${version}.tgz`
|
||||
@@ -43,13 +58,15 @@ program
|
||||
|
||||
spinner.start()
|
||||
|
||||
await checkLatestVersion(version)
|
||||
|
||||
await download(templateURL, currentDirectory, { extract: true })
|
||||
|
||||
renameSync(`${currentDirectory}/package`, `${currentDirectory}/${projectDirectory}`)
|
||||
|
||||
spinner.stop()
|
||||
|
||||
console.info(` ${logSymbols.success}`, `Your project is ready!\n`)
|
||||
console.info(`\n ${logSymbols.success}`, `Your project is ready!\n`)
|
||||
console.info(` Please, install your dependencies by running:\n`)
|
||||
console.info(` ${chalk.cyan("cd")} ${projectDirectory}`)
|
||||
console.info(` ${chalk.cyan("npm i")}\n`)
|
||||
@@ -64,58 +81,134 @@ program
|
||||
.map((s) => ` ${chalk.cyan(`npm run ${s}`)}`)
|
||||
.join("\n")}\n`
|
||||
)
|
||||
|
||||
console.info(` See the README.md file to understand how to use them!\n`)
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command("get-groups")
|
||||
.description("Get the list of groups from a supported network (goerli or arbitrum).")
|
||||
.option("-n, --network <network-name>", "Supported Ethereum network.", "goerli")
|
||||
.option("-n, --network <network-name>", "Supported Ethereum network.")
|
||||
.allowExcessArguments(false)
|
||||
.action(async ({ network }) => {
|
||||
if (!["goerli", "arbitrum"].includes(network)) {
|
||||
if (!network) {
|
||||
const { selectedNetwork } = await inquirer.prompt({
|
||||
name: "selectedNetwork",
|
||||
type: "list",
|
||||
message: "Select one of our supported networks:",
|
||||
default: supportedNetworks[0],
|
||||
choices: supportedNetworks
|
||||
})
|
||||
network = selectedNetwork
|
||||
}
|
||||
|
||||
if (!supportedNetworks.includes(network)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
|
||||
return
|
||||
}
|
||||
|
||||
const subgraph = new Subgraph(network)
|
||||
const subgraph = new SemaphoreSubgraph(network)
|
||||
const spinner = new Spinner("Fetching groups")
|
||||
|
||||
spinner.start()
|
||||
|
||||
try {
|
||||
const groups = await subgraph.getGroups()
|
||||
const groupIds = await subgraph.getGroupIds()
|
||||
|
||||
spinner.stop()
|
||||
|
||||
if (groups.length === 0) {
|
||||
console.info(` ${logSymbols.error}`, "error: there are no groups in this network\n")
|
||||
if (groupIds.length === 0) {
|
||||
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
|
||||
return
|
||||
}
|
||||
|
||||
const content = `${groups.map(({ id }: any) => ` - ${id}`).join("\n")}`
|
||||
const content = `\n${groupIds.map((id: any) => ` - ${id}`).join("\n")}`
|
||||
|
||||
console.info(`${content}\n`)
|
||||
} catch (error) {
|
||||
spinner.stop()
|
||||
|
||||
console.info(` ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
|
||||
}
|
||||
})
|
||||
|
||||
program
|
||||
.command("get-group")
|
||||
.description("Get the data of a group from a supported network (Goerli or Arbitrum).")
|
||||
.argument("<group-id>", "Identifier of the group.")
|
||||
.option("-n, --network <network-name>", "Supported Ethereum network.", "goerli")
|
||||
.option("--members", "Show group members.")
|
||||
.option("--signals", "Show group signals.")
|
||||
.argument("[group-id]", "Identifier of the group.")
|
||||
.option("-n, --network <network-name>", "Supported Ethereum network.")
|
||||
.option("-m, --members", "Show group members.")
|
||||
.option("-s, --signals", "Show group signals.")
|
||||
.allowExcessArguments(false)
|
||||
.action(async (groupId, { network, members, signals }) => {
|
||||
if (!["goerli", "arbitrum"].includes(network)) {
|
||||
if (!network) {
|
||||
const { selectedNetwork } = await inquirer.prompt({
|
||||
name: "selectedNetwork",
|
||||
type: "list",
|
||||
message: "Select one of our supported networks:",
|
||||
default: supportedNetworks[0],
|
||||
choices: supportedNetworks
|
||||
})
|
||||
network = selectedNetwork
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
const subgraphGroups = new SemaphoreSubgraph(network)
|
||||
const spinnerGroups = new Spinner("Fetching groups")
|
||||
spinnerGroups.start()
|
||||
try {
|
||||
const groups = await subgraphGroups.getGroups()
|
||||
|
||||
spinnerGroups.stop()
|
||||
|
||||
if (groups.length === 0) {
|
||||
console.info(`\n ${logSymbols.info}`, "info: there are no groups in this network\n")
|
||||
return
|
||||
}
|
||||
|
||||
const groupIds = groups.map(({ id }: any) => id)
|
||||
|
||||
const { selectedGroupId } = await inquirer.prompt({
|
||||
name: "selectedGroupId",
|
||||
type: "list",
|
||||
message: "Select one of the following existing group ids:",
|
||||
choices: groupIds
|
||||
})
|
||||
groupId = selectedGroupId
|
||||
} catch (error) {
|
||||
spinnerGroups.stop()
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!members && !signals) {
|
||||
const { showMembers } = await inquirer.prompt({
|
||||
name: "showMembers",
|
||||
type: "confirm",
|
||||
message: "Do you want to show members?",
|
||||
default: false
|
||||
})
|
||||
|
||||
members = showMembers
|
||||
|
||||
const { showSignals } = await inquirer.prompt({
|
||||
name: "showSignals",
|
||||
type: "confirm",
|
||||
message: "Do you want to show signals?",
|
||||
default: false
|
||||
})
|
||||
|
||||
signals = showSignals
|
||||
}
|
||||
|
||||
if (!supportedNetworks.includes(network)) {
|
||||
console.info(`\n ${logSymbols.error}`, `error: the network '${network}' is not supported\n`)
|
||||
return
|
||||
}
|
||||
|
||||
const subgraph = new Subgraph(network)
|
||||
const subgraph = new SemaphoreSubgraph(network)
|
||||
const spinner = new Spinner(`Fetching group ${groupId}`)
|
||||
|
||||
spinner.start()
|
||||
@@ -126,7 +219,7 @@ program
|
||||
spinner.stop()
|
||||
|
||||
if (!group) {
|
||||
console.info(` ${logSymbols.error}`, "error: the group does not exist\n")
|
||||
console.info(`\n ${logSymbols.error}`, "error: the group does not exist\n")
|
||||
|
||||
return
|
||||
}
|
||||
@@ -151,12 +244,12 @@ program
|
||||
.join("\n")}`
|
||||
}
|
||||
|
||||
console.info(`${content}\n`)
|
||||
console.info(`\n${content}\n`)
|
||||
} catch (error) {
|
||||
spinner.stop()
|
||||
|
||||
console.info(` ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
|
||||
console.info(`\n ${logSymbols.error}`, "error: unexpected error with the Semaphore subgraph")
|
||||
}
|
||||
})
|
||||
|
||||
program.parse()
|
||||
program.parse(process.argv)
|
||||
|
||||
@@ -20,6 +20,6 @@ export default class Spinner {
|
||||
stop() {
|
||||
this.ora.stop()
|
||||
|
||||
process.stdout.cursorTo(0)
|
||||
process.stdout.moveCursor(0, -1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
@@ -92,29 +92,29 @@ yarn test:report-gas
|
||||
|
||||
### Deploy contracts
|
||||
|
||||
Deploy a verifier contract with depth = 20:
|
||||
Deploy the `Semaphore.sol` contract without any parameter:
|
||||
|
||||
```bash
|
||||
yarn deploy:verifier --depth 20
|
||||
yarn deploy:semaphore
|
||||
```
|
||||
|
||||
Deploy the `Semaphore.sol` contract with one verifier:
|
||||
or deploy it by providing the addresses of the contracts/libraries on which it depends:
|
||||
|
||||
```bash
|
||||
yarn deploy:semaphore --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]'
|
||||
yarn deploy:semaphore --semaphoreVerifier <address>
|
||||
```
|
||||
|
||||
Deploy all verifiers and Semaphore contract:
|
||||
> **Note**
|
||||
> Run `yarn deploy:semaphore --help` to see the complete list.
|
||||
|
||||
If you want to deploy your contract in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as an option:
|
||||
|
||||
```bash
|
||||
yarn deploy:all
|
||||
```
|
||||
|
||||
If you want to deploy contracts in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as option:
|
||||
|
||||
```bash
|
||||
yarn deploy:all --network goerli
|
||||
yarn deploy:all --network localhost
|
||||
yarn deploy:semaphore --network goerli
|
||||
yarn deploy:semaphore --network sepolia
|
||||
yarn deploy:semaphore --network mumbai
|
||||
yarn deploy:semaphore --network optimism-goerli
|
||||
yarn deploy:semaphore --network arbitrum
|
||||
```
|
||||
|
||||
If you want to deploy contracts on Goerli or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
pragma solidity ^0.8.4;
|
||||
|
||||
library Pairing {
|
||||
error Semaphore__InvalidProof();
|
||||
error InvalidProof();
|
||||
|
||||
// The prime q in the base field F_q for G1
|
||||
uint256 constant BASE_MODULUS = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
@@ -56,7 +56,7 @@ library Pairing {
|
||||
|
||||
// Validate input or revert
|
||||
if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
|
||||
// We know p.Y > 0 and p.Y < BASE_MODULUS.
|
||||
@@ -82,7 +82,7 @@ library Pairing {
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ library Pairing {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
if (s >= SCALAR_MODULUS) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
|
||||
uint256[3] memory input;
|
||||
@@ -109,7 +109,7 @@ library Pairing {
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ library Pairing {
|
||||
// By EIP-197 all input is verified to be less than the BASE_MODULUS and form elements in their
|
||||
// respective groups of the right order.
|
||||
if (p1.length != p2.length) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
|
||||
uint256 elements = p1.length;
|
||||
@@ -145,7 +145,7 @@ library Pairing {
|
||||
}
|
||||
|
||||
if (!success || out[0] != 1) {
|
||||
revert Semaphore__InvalidProof();
|
||||
revert InvalidProof();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/contracts",
|
||||
"version": "2.6.1",
|
||||
"version": "3.2.3",
|
||||
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"Semaphore.sol",
|
||||
"**/*.sol"
|
||||
],
|
||||
"keywords": [
|
||||
|
||||
@@ -1,24 +1,7 @@
|
||||
{
|
||||
"verifiers": {
|
||||
"Verifier16": "0x6143ECd9Fd1A00EDe1046d456f8aab53a7D71609",
|
||||
"Verifier17": "0xAc12fFFE354D6446eb50dd33E683B78FED73Fb02",
|
||||
"Verifier18": "0x610aeF0F2da3CD1C8bDefe4BDB434Ee146E0C701",
|
||||
"Verifier19": "0x5477725177035bbC9d70443eb921D29749D6FCb4",
|
||||
"Verifier20": "0x3fB2C0988a37b76e760c44e6516aF720935f3136",
|
||||
"Verifier21": "0xDc8f6B8A42836d4566256f4c6C53131DFD127DF8",
|
||||
"Verifier22": "0x6962b5e706be5278eeCb01c286b50A48484632f2",
|
||||
"Verifier23": "0x41e4796Bd89B4BF04013b559c93fC32E9a2BdF6B",
|
||||
"Verifier24": "0xD528B1D1408ab3583af4694F92b0aFEbE33d5b60",
|
||||
"Verifier25": "0x1683a27EF9c10c5286dB56412E1272cD0Ca733e7",
|
||||
"Verifier26": "0x78194bB665d1E33b97eE45B1A755c15717E94C00",
|
||||
"Verifier27": "0x997Dac00E6701Ef7F3518280E5a9922801126E42",
|
||||
"Verifier28": "0xDd3C7f4cBA2467aE41c0F614A3c3E24bC80268c6",
|
||||
"Verifier29": "0xe53eF12093933D5df5691EAbA3821bD1c1EB60Cd",
|
||||
"Verifier30": "0x7FeA07c536ABBB0E7FB3c833376EE4EaDc21340e",
|
||||
"Verifier31": "0xe4539a592df18936202480FBe77E47DE012F2178",
|
||||
"Verifier32": "0x98c90845A7870e215cBd7265DDC653E6c07032F4"
|
||||
},
|
||||
"Semaphore": "0x86337c87A56117f8264bbaBA70e5a522C6E8A604",
|
||||
"PoseidonT3": "0xe0c8d1e53D9Bfc9071F6564755FCFf6cC0dB61d0",
|
||||
"IncrementalBinaryTree": "0x91cD2B8573629d00BeC72EA1188d446897BD3948"
|
||||
"Pairing": "0xE3a4C2FE9f025405cA6F60f6E960B4558604A74C",
|
||||
"SemaphoreVerifier": "0xCAbeED6cB96a287000aBd834b0B79c05e6Ea4d07",
|
||||
"Poseidon": "0xe0c8d1e53D9Bfc9071F6564755FCFf6cC0dB61d0",
|
||||
"IncrementalBinaryTree": "0xcDF8efE6334c68aF283C83f2F14648da51fcfFb0",
|
||||
"Semaphore": "0x72dca3c971136bf47BACF16A141f0fcfAC925aeC"
|
||||
}
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
{
|
||||
"verifiers": {
|
||||
"Verifier16": "0xA5253ba39381Aa99c4C2C5A4D5C2deC036d06629",
|
||||
"Verifier17": "0xe0418A5f8fBF051D6cbc41Ff29855Dd2a02201Ab",
|
||||
"Verifier18": "0x7CdB3336d7d7c55Bce0FB1508594C54521656797",
|
||||
"Verifier19": "0xbd870921d8A5398a3314C950d1fc63b8C3AB190B",
|
||||
"Verifier20": "0x2a96c5696F85e3d2aa918496806B5c5a4D93E099",
|
||||
"Verifier21": "0x5Ec7d851a52A2a25CEc528F42a7ACA8EcF4667Cd",
|
||||
"Verifier22": "0x919d3d9c05FA7411e334deA5a763354fC7B6aA5b",
|
||||
"Verifier23": "0x63917b00a6dA7865bEfdd107AfC83CC2e6BDE552",
|
||||
"Verifier24": "0xd05CAd7d940114c1419098EE3cEA0776ab510E7D",
|
||||
"Verifier25": "0x6D9862e6140D94E932d94c8BcE74a0BDD0ea5ACb",
|
||||
"Verifier26": "0x8c29e0b77e32f704F03eeCE01c041192A5EB6c77",
|
||||
"Verifier27": "0x066cC22f8CA2A8D90D7Ff77D8a10A27e629c9c4C",
|
||||
"Verifier28": "0x698F9507f504E2BD238be7da56E8D9fee60C6D15",
|
||||
"Verifier29": "0xbBfC2E201C3c3c6F50063c3Edb4746c6Fcb36346",
|
||||
"Verifier30": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1",
|
||||
"Verifier31": "0x133b69Ce47BF20C49368354914DF47519Ca6cCFE",
|
||||
"Verifier32": "0xe2978F79cb4AF62e5C990EE5c7E12fb22ee22e2D"
|
||||
},
|
||||
"Semaphore": "0x5259d32659F1806ccAfcE593ED5a89eBAb85262f",
|
||||
"Semaphore": "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7",
|
||||
"PoseidonT3": "0xe0A452533853310C371b50Bd91BB9DCC8961350F",
|
||||
"IncrementalBinaryTree": "0x61AE89E372492e53D941DECaaC9821649fa9B236"
|
||||
"IncrementalBinaryTree": "0x9Ed9f58CA9212Ddf0377C8C4Cd089748F9337820",
|
||||
"Pairing": "0xE9c41c912CF750D79Cf304a196d4Bc8Dfd626C86",
|
||||
"SemaphoreVerifier": "0x66e772B0B8Ee1c24E4b6aC99A3A82C77f431792E",
|
||||
"Poseidon": "0xe0A452533853310C371b50Bd91BB9DCC8961350F"
|
||||
}
|
||||
|
||||
7
packages/contracts/deployed-contracts/mumbai.json
Normal file
7
packages/contracts/deployed-contracts/mumbai.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Pairing": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
|
||||
"SemaphoreVerifier": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
|
||||
"Poseidon": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
|
||||
"IncrementalBinaryTree": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F",
|
||||
"Semaphore": "0xF864ABa335073e01234c9a88888BfFfa965650bD"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Pairing": "0xEFD83f827FA5B0496359D817c6CD8a5AA5D2aCe5",
|
||||
"SemaphoreVerifier": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
|
||||
"Poseidon": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
|
||||
"IncrementalBinaryTree": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
|
||||
"Semaphore": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
|
||||
}
|
||||
7
packages/contracts/deployed-contracts/sepolia.json
Normal file
7
packages/contracts/deployed-contracts/sepolia.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"Pairing": "0xEFD83f827FA5B0496359D817c6CD8a5AA5D2aCe5",
|
||||
"SemaphoreVerifier": "0x3d3df6CFc6BFf68d9693e097F32bF4a9903E77a5",
|
||||
"Poseidon": "0x5f4edC58142f4395D1D536e793137A0252dA5a49",
|
||||
"IncrementalBinaryTree": "0x181B7f34538cE3BceC68597d4A212aB3f7881648",
|
||||
"Semaphore": "0x220fBdB6F996827b1Cf12f0C181E8d5e6de3a36F"
|
||||
}
|
||||
@@ -30,8 +30,23 @@ function getNetworks(): NetworksUserConfig {
|
||||
chainId: 5,
|
||||
accounts
|
||||
},
|
||||
sepolia: {
|
||||
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 11155111,
|
||||
accounts
|
||||
},
|
||||
mumbai: {
|
||||
url: `https://polygon-mumbai.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 80001,
|
||||
accounts
|
||||
},
|
||||
"optimism-goerli": {
|
||||
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 420,
|
||||
accounts
|
||||
},
|
||||
arbitrum: {
|
||||
url: "https://arb1.arbitrum.io/rpc",
|
||||
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
|
||||
chainId: 42161,
|
||||
accounts
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
import { readFileSync, writeFileSync } from "fs"
|
||||
|
||||
type DeployedContracts = {
|
||||
Pairing?: string
|
||||
SemaphoreVerifier?: string
|
||||
Poseidon?: string
|
||||
IncrementalBinaryTree?: string
|
||||
Semaphore?: string
|
||||
Pairing: string
|
||||
SemaphoreVerifier: string
|
||||
Poseidon: string
|
||||
IncrementalBinaryTree: string
|
||||
Semaphore: string
|
||||
}
|
||||
|
||||
export function getDeployedContracts(network: string | undefined): DeployedContracts | null {
|
||||
try {
|
||||
return JSON.parse(readFileSync(`./deployed-contracts/${network}.json`, "utf8"))
|
||||
} catch (error) {
|
||||
return {}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function saveDeployedContracts(network: string | undefined, newDeployedContracts: DeployedContracts) {
|
||||
const deployedContracts = getDeployedContracts(network)
|
||||
|
||||
writeFileSync(
|
||||
`./deployed-contracts/${network}.json`,
|
||||
JSON.stringify(
|
||||
{
|
||||
...deployedContracts,
|
||||
...newDeployedContracts
|
||||
},
|
||||
null,
|
||||
4
|
||||
)
|
||||
)
|
||||
export function saveDeployedContracts(network: string | undefined, deployedContracts: DeployedContracts) {
|
||||
writeFileSync(`./deployed-contracts/${network}.json`, JSON.stringify(deployedContracts, null, 4))
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import { hardhatArguments, run } from "hardhat"
|
||||
import { getDeployedContracts } from "./utils"
|
||||
|
||||
async function verify(address: string, constructorArguments?: any[]): Promise<void> {
|
||||
try {
|
||||
await run("verify:verify", {
|
||||
address,
|
||||
constructorArguments
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const deployedContracts = getDeployedContracts(hardhatArguments.network)
|
||||
|
||||
await run("verify:verify", {
|
||||
address: deployedContracts.IncrementalBinaryTree
|
||||
})
|
||||
|
||||
await run("verify:verify", {
|
||||
address: deployedContracts.Semaphore
|
||||
})
|
||||
|
||||
await run("verify:verify", {
|
||||
address: deployedContracts.Pairing
|
||||
})
|
||||
|
||||
await run("verify:verify", {
|
||||
address: deployedContracts.SemaphoreVerifier
|
||||
})
|
||||
if (deployedContracts) {
|
||||
await verify(deployedContracts.IncrementalBinaryTree)
|
||||
await verify(deployedContracts.Pairing)
|
||||
await verify(deployedContracts.SemaphoreVerifier)
|
||||
await verify(deployedContracts.Semaphore, [deployedContracts.SemaphoreVerifier])
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
159
packages/data/README.md
Normal file
@@ -0,0 +1,159 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore data
|
||||
</h1>
|
||||
<p align="center">A library to query Semaphore contracts.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol">
|
||||
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@semaphore-protocol/data">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/data?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/data">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/data.svg?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://eslint.org/">
|
||||
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
|
||||
</a>
|
||||
<a href="https://prettier.io/">
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This library allows you to query the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/contracts/Semaphore.sol) contract data (i.e. groups) using the [Semaphore subgraph](https://github.com/semaphore-protocol/subgraph) or Ethers. It can be used on Node.js and browsers. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/data` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/data
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/data
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
\# **new SemaphoreSubgraph**(networkOrSubgraphURL: _Network_ | _string_ = "goerli"): _SemaphoreSubgraph_
|
||||
|
||||
```typescript
|
||||
import { SemaphoreSubgraph } from "@semaphore-protocol/data"
|
||||
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph()
|
||||
|
||||
// or:
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph("arbitrum")
|
||||
|
||||
// or:
|
||||
const semaphoreSubgraph = new SemaphoreSubgraph(
|
||||
"https://api.studio.thegraph.com/query/14377/<your-subgraph>/<your-version>"
|
||||
)
|
||||
```
|
||||
|
||||
\# **getGroupIds**(): _Promise\<string[]>_
|
||||
|
||||
```typescript
|
||||
const groupIds = await semaphoreSubgraph.getGroupIds()
|
||||
```
|
||||
|
||||
\# **getGroups**(options?: _GroupOptions_): _Promise\<GroupResponse[]>_
|
||||
|
||||
```typescript
|
||||
const groups = await semaphoreSubgraph.getGroups()
|
||||
|
||||
// or
|
||||
|
||||
const groups = await semaphoreSubgraph.getGroups({ members: true, verifiedProofs: true })
|
||||
```
|
||||
|
||||
\# **getGroup**(groupId: _string_, options?: _GroupOptions_): _Promise\<GroupResponse>_
|
||||
|
||||
```typescript
|
||||
const group = await semaphoreSubgraph.getGroup("42")
|
||||
|
||||
// or
|
||||
|
||||
const { members, verifiedProofs } = semaphoreSubgraph.getGroup("42", { members: true, verifiedProofs: true })
|
||||
```
|
||||
|
||||
\# **new Ethers**(networkOrEthereumURL: Network | string = "goerli", options: EthersOptions = {}): _SemaphoreEthers_
|
||||
|
||||
```typescript
|
||||
import { SemaphoreEthers } from "@semaphore-protocol/data"
|
||||
|
||||
const semaphoreEthers = new SemaphoreEthers()
|
||||
|
||||
// or:
|
||||
const semaphoreEthers = new SemaphoreEthers("homestead", {
|
||||
address: "semaphore-address",
|
||||
startBlock: 0
|
||||
})
|
||||
|
||||
// or:
|
||||
const semaphoreEthers = new SemaphoreEthers("http://localhost:8545", {
|
||||
address: "semaphore-address"
|
||||
})
|
||||
```
|
||||
|
||||
\# **getGroupIds**(): _Promise\<string[]>_
|
||||
|
||||
```typescript
|
||||
const groupIds = await semaphoreEthers.getGroupIds()
|
||||
```
|
||||
|
||||
\# **getGroup**(groupId: _string_): _Promise\<GroupResponse>_
|
||||
|
||||
```typescript
|
||||
const group = await semaphoreEthers.getGroup("42")
|
||||
```
|
||||
|
||||
\# **getGroupAdmin**(groupId: _string_): _Promise\<string>_
|
||||
|
||||
```typescript
|
||||
const admin = await semaphoreEthers.getGroupAdmin("42")
|
||||
```
|
||||
|
||||
\# **getGroupMembers**(groupId: _string_): _Promise\<string[]>_
|
||||
|
||||
```typescript
|
||||
const members = await semaphoreEthers.getGroupMembers("42")
|
||||
```
|
||||
|
||||
\# **getGroupVerifiedProofs**(groupId: _string_): _Promise\<any[]>_
|
||||
|
||||
```typescript
|
||||
const verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs()
|
||||
```
|
||||
@@ -1,11 +1,8 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/subgraph",
|
||||
"version": "2.6.1",
|
||||
"name": "@semaphore-protocol/data",
|
||||
"version": "3.2.3",
|
||||
"description": "A library to query Semaphore contracts.",
|
||||
"license": "MIT",
|
||||
"iife": "dist/index.js",
|
||||
"unpkg": "dist/index.min.js",
|
||||
"jsdelivr": "dist/index.min.js",
|
||||
"main": "dist/index.node.js",
|
||||
"exports": {
|
||||
"import": "./dist/index.mjs",
|
||||
@@ -19,7 +16,7 @@
|
||||
"README.md"
|
||||
],
|
||||
"repository": "https://github.com/semaphore-protocol/semaphore",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/subgraph",
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/data",
|
||||
"bugs": {
|
||||
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
|
||||
},
|
||||
@@ -27,18 +24,20 @@
|
||||
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
|
||||
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
|
||||
"prepublishOnly": "yarn build",
|
||||
"docs": "typedoc src/index.ts --out ../../docs/subgraph"
|
||||
"docs": "typedoc src/index.ts --out ../../docs/data"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersproject/contracts": "^5.7.0",
|
||||
"@ethersproject/providers": "^5.7.0",
|
||||
"axios": "^0.27.2"
|
||||
}
|
||||
}
|
||||
@@ -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" })
|
||||
]
|
||||
286
packages/data/src/ethers.test.ts
Normal file
286
packages/data/src/ethers.test.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
import SemaphoreEthers from "./ethers"
|
||||
import getEvents from "./getEvents"
|
||||
|
||||
jest.mock("./getEvents", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn()
|
||||
}))
|
||||
|
||||
jest.mock("@ethersproject/contracts", () => ({
|
||||
__esModule: true,
|
||||
Contract: jest.fn(
|
||||
() =>
|
||||
({
|
||||
getMerkleTreeRoot: () => "222",
|
||||
getNumberOfMerkleTreeLeaves: () => ({
|
||||
toNumber: () => 2
|
||||
})
|
||||
} as any)
|
||||
)
|
||||
}))
|
||||
|
||||
const getEventsMocked = getEvents as jest.MockedFunction<typeof getEvents>
|
||||
|
||||
describe("SemaphoreEthers", () => {
|
||||
let semaphore: SemaphoreEthers
|
||||
|
||||
describe("# SemaphoreEthers", () => {
|
||||
it("Should instantiate a SemaphoreEthers object with different networks", () => {
|
||||
semaphore = new SemaphoreEthers()
|
||||
const semaphore1 = new SemaphoreEthers("arbitrum")
|
||||
const semaphore2 = new SemaphoreEthers("matic")
|
||||
const semaphore3 = new SemaphoreEthers("homestead", {
|
||||
address: "0x0000000000000000000000000000000000000000",
|
||||
startBlock: 0
|
||||
})
|
||||
|
||||
expect(semaphore.network).toBe("goerli")
|
||||
expect(semaphore.contract).toBeInstanceOf(Object)
|
||||
expect(semaphore1.network).toBe("arbitrum")
|
||||
expect(semaphore2.network).toBe("maticmum")
|
||||
expect(semaphore3.network).toBe("homestead")
|
||||
expect(semaphore3.options.startBlock).toBe(0)
|
||||
expect(semaphore3.options.address).toContain("0x000000")
|
||||
})
|
||||
|
||||
it("Should instantiate a SemaphoreEthers object with different providers", () => {
|
||||
const semaphore1 = new SemaphoreEthers("homestead", {
|
||||
provider: "infura",
|
||||
address: "0x0000000000000000000000000000000000000000",
|
||||
apiKey: "1234567890"
|
||||
})
|
||||
const semaphore2 = new SemaphoreEthers("homestead", {
|
||||
provider: "etherscan",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
const semaphore3 = new SemaphoreEthers("homestead", {
|
||||
provider: "alchemy",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
const semaphore4 = new SemaphoreEthers("homestead", {
|
||||
provider: "cloudflare",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
const semaphore5 = new SemaphoreEthers("homestead", {
|
||||
provider: "pocket",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
const semaphore6 = new SemaphoreEthers("homestead", {
|
||||
provider: "ankr",
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
|
||||
expect(semaphore1.options.provider).toBe("infura")
|
||||
expect(semaphore1.options.apiKey).toBe("1234567890")
|
||||
expect(semaphore2.options.provider).toBe("etherscan")
|
||||
expect(semaphore3.options.provider).toBe("alchemy")
|
||||
expect(semaphore4.options.provider).toBe("cloudflare")
|
||||
expect(semaphore5.options.provider).toBe("pocket")
|
||||
expect(semaphore6.options.provider).toBe("ankr")
|
||||
})
|
||||
|
||||
it("Should instantiate a SemaphoreEthers object with a custom URL", () => {
|
||||
const semaphore1 = new SemaphoreEthers("http://localhost:8545", {
|
||||
address: "0x0000000000000000000000000000000000000000"
|
||||
})
|
||||
|
||||
expect(semaphore1.network).toBe("http://localhost:8545")
|
||||
})
|
||||
|
||||
it("Should throw an error if the network is not supported by Semaphore yet and there's no address", () => {
|
||||
const fun = () => new SemaphoreEthers("homestead")
|
||||
|
||||
expect(fun).toThrow("You should provide a Semaphore contract address for this network")
|
||||
})
|
||||
|
||||
it("Should throw an error if the provider is not supported", () => {
|
||||
const fun = () =>
|
||||
new SemaphoreEthers("goerli", {
|
||||
provider: "hello" as any
|
||||
})
|
||||
|
||||
expect(fun).toThrow("Provider 'hello' is not supported")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupIds", () => {
|
||||
it("Should return all the existing groups", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([["32"], ["42"]]))
|
||||
|
||||
const groupIds = await semaphore.getGroupIds()
|
||||
|
||||
expect(groupIds).toContain("42")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroup", () => {
|
||||
it("Should return a specific group", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
merkleTreeDepth: "20",
|
||||
zeroValue: "111"
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const group = await semaphore.getGroup("42")
|
||||
|
||||
expect(group.merkleTree.depth).toBe("20")
|
||||
expect(group.merkleTree.root).toBe("222")
|
||||
expect(group.merkleTree.zeroValue).toContain("111")
|
||||
})
|
||||
|
||||
it("Should throw an error if the group does not exist", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
|
||||
|
||||
const fun = () => semaphore.getGroup("666")
|
||||
|
||||
await expect(fun).rejects.toThrow("Group '666' not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupAdmin", () => {
|
||||
it("Should return a group admin", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
newAdmin: "0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F"
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const admin = await semaphore.getGroupAdmin("42")
|
||||
|
||||
expect(admin).toBe("0xA9C2B639a28cDa8b59C4377e980F75A93dD8605F")
|
||||
})
|
||||
|
||||
it("Should throw an error if the group does not exist", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
|
||||
|
||||
const fun = () => semaphore.getGroupAdmin("666")
|
||||
|
||||
await expect(fun).rejects.toThrow("Group '666' not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupMembers", () => {
|
||||
it("Should return a list of group members", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
merkleTreeDepth: "20",
|
||||
zeroValue: "0"
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
index: "0",
|
||||
merkleTreeRoot: "223",
|
||||
blockNumber: 3
|
||||
},
|
||||
{
|
||||
index: "2",
|
||||
merkleTreeRoot: "224",
|
||||
blockNumber: 4
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
index: "1",
|
||||
newIdentityCommitment: "113",
|
||||
merkleTreeRoot: "225",
|
||||
blockNumber: 3
|
||||
},
|
||||
{
|
||||
index: "2",
|
||||
newIdentityCommitment: "114",
|
||||
merkleTreeRoot: "226",
|
||||
blockNumber: 3
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
index: "0",
|
||||
identityCommitment: "110",
|
||||
merkleTreeRoot: "220",
|
||||
blockNumber: 0
|
||||
},
|
||||
{
|
||||
index: "1",
|
||||
identityCommitment: "111",
|
||||
merkleTreeRoot: "221",
|
||||
blockNumber: 1
|
||||
},
|
||||
{
|
||||
index: "2",
|
||||
identityCommitment: "112",
|
||||
merkleTreeRoot: "222",
|
||||
blockNumber: 2
|
||||
},
|
||||
{
|
||||
index: "3",
|
||||
identityCommitment: "113",
|
||||
merkleTreeRoot: "223",
|
||||
blockNumber: 3
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const members = await semaphore.getGroupMembers("42")
|
||||
|
||||
expect(members[0]).toBe("0")
|
||||
expect(members[1]).toBe("113")
|
||||
expect(members[2]).toBe("0")
|
||||
})
|
||||
|
||||
it("Should throw an error if the group does not exist", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
|
||||
|
||||
const fun = () => semaphore.getGroupMembers("666")
|
||||
|
||||
await expect(fun).rejects.toThrow("Group '666' not found")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupVerifiedProofs", () => {
|
||||
it("Should return a list of group verified proofs", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
merkleTreeDepth: "20",
|
||||
zeroValue: "0"
|
||||
}
|
||||
])
|
||||
)
|
||||
getEventsMocked.mockReturnValueOnce(
|
||||
Promise.resolve([
|
||||
{
|
||||
signal: "111",
|
||||
merkleTreeRoot: "112",
|
||||
externalNullifier: "113",
|
||||
nullifierHash: "114"
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
const [verifiedProof] = await semaphore.getGroupVerifiedProofs("42")
|
||||
|
||||
expect(verifiedProof.signal).toContain("111")
|
||||
})
|
||||
|
||||
it("Should throw an error if the group does not exist", async () => {
|
||||
getEventsMocked.mockReturnValueOnce(Promise.resolve([]))
|
||||
|
||||
const fun = () => semaphore.getGroupVerifiedProofs("666")
|
||||
|
||||
await expect(fun).rejects.toThrow("Group '666' not found")
|
||||
})
|
||||
})
|
||||
})
|
||||
269
packages/data/src/ethers.ts
Normal file
269
packages/data/src/ethers.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
import { Contract } from "@ethersproject/contracts"
|
||||
import {
|
||||
AlchemyProvider,
|
||||
AnkrProvider,
|
||||
CloudflareProvider,
|
||||
EtherscanProvider,
|
||||
InfuraProvider,
|
||||
JsonRpcProvider,
|
||||
PocketProvider,
|
||||
Provider
|
||||
} from "@ethersproject/providers"
|
||||
import checkParameter from "./checkParameter"
|
||||
import getEvents from "./getEvents"
|
||||
import SemaphoreABI from "./semaphoreABI.json"
|
||||
import { EthersOptions, GroupResponse, Network } from "./types"
|
||||
|
||||
export default class SemaphoreEthers {
|
||||
private _network: Network | string
|
||||
private _options: EthersOptions
|
||||
private _contract: Contract
|
||||
|
||||
/**
|
||||
* Initializes the Ethers object with an Ethereum network or custom URL.
|
||||
* @param networkOrEthereumURL Ethereum network or custom URL.
|
||||
* @param options Ethers options.
|
||||
*/
|
||||
constructor(networkOrEthereumURL: Network | string = "goerli", options: EthersOptions = {}) {
|
||||
checkParameter(networkOrEthereumURL, "networkOrSubgraphURL", "string")
|
||||
|
||||
if (options.provider) {
|
||||
checkParameter(options.provider, "provider", "string")
|
||||
} else if (!networkOrEthereumURL.startsWith("http")) {
|
||||
options.provider = "infura"
|
||||
}
|
||||
|
||||
if (options.apiKey) {
|
||||
checkParameter(options.apiKey, "apiKey", "string")
|
||||
}
|
||||
|
||||
if (networkOrEthereumURL === "matic") {
|
||||
networkOrEthereumURL = "maticmum"
|
||||
}
|
||||
|
||||
switch (networkOrEthereumURL) {
|
||||
case "arbitrum":
|
||||
options.address = "0x72dca3c971136bf47BACF16A141f0fcfAC925aeC"
|
||||
options.startBlock = 54934350
|
||||
break
|
||||
case "maticmum":
|
||||
options.address = "0xF864ABa335073e01234c9a88888BfFfa965650bD"
|
||||
options.startBlock = 32902215
|
||||
break
|
||||
case "goerli":
|
||||
options.address = "0x89490c95eD199D980Cdb4FF8Bac9977EDb41A7E7"
|
||||
options.startBlock = 8255063
|
||||
break
|
||||
default:
|
||||
if (options.address === undefined) {
|
||||
throw new Error(`You should provide a Semaphore contract address for this network`)
|
||||
}
|
||||
|
||||
if (options.startBlock === undefined) {
|
||||
options.startBlock = 0
|
||||
}
|
||||
}
|
||||
|
||||
let provider: Provider
|
||||
|
||||
switch (options.provider) {
|
||||
case "infura":
|
||||
provider = new InfuraProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "alchemy":
|
||||
provider = new AlchemyProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "cloudflare":
|
||||
provider = new CloudflareProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "etherscan":
|
||||
provider = new EtherscanProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "pocket":
|
||||
provider = new PocketProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
case "ankr":
|
||||
provider = new AnkrProvider(networkOrEthereumURL, options.apiKey)
|
||||
break
|
||||
default:
|
||||
if (!networkOrEthereumURL.startsWith("http")) {
|
||||
throw new Error(`Provider '${options.provider}' is not supported`)
|
||||
}
|
||||
|
||||
provider = new JsonRpcProvider(networkOrEthereumURL)
|
||||
}
|
||||
|
||||
this._network = networkOrEthereumURL
|
||||
this._options = options
|
||||
this._contract = new Contract(options.address, SemaphoreABI, provider)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Ethereum network or custom URL.
|
||||
* @returns Ethereum network or custom URL.
|
||||
*/
|
||||
get network(): Network | string {
|
||||
return this._network
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Ethers options.
|
||||
* @returns Ethers options.
|
||||
*/
|
||||
get options(): EthersOptions {
|
||||
return this._options
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contract object.
|
||||
* @returns Contract object.
|
||||
*/
|
||||
get contract(): Contract {
|
||||
return this._contract
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of group ids.
|
||||
* @returns List of group ids.
|
||||
*/
|
||||
async getGroupIds(): Promise<string[]> {
|
||||
const groups = await getEvents(this._contract, "GroupCreated", [], this._options.startBlock)
|
||||
|
||||
return groups.map((event: any) => event[0].toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific group.
|
||||
* @param groupId Group id.
|
||||
* @returns Specific group.
|
||||
*/
|
||||
async getGroup(groupId: string): Promise<GroupResponse> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
|
||||
|
||||
if (!groupCreatedEvent) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
const merkleTreeRoot = await this._contract.getMerkleTreeRoot(groupId)
|
||||
const numberOfLeaves = await this._contract.getNumberOfMerkleTreeLeaves(groupId)
|
||||
|
||||
const group: GroupResponse = {
|
||||
id: groupId,
|
||||
merkleTree: {
|
||||
depth: groupCreatedEvent.merkleTreeDepth.toString(),
|
||||
zeroValue: groupCreatedEvent.zeroValue.toString(),
|
||||
numberOfLeaves: numberOfLeaves.toNumber(),
|
||||
root: merkleTreeRoot.toString()
|
||||
}
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a group admin.
|
||||
* @param groupId Group id.
|
||||
* @returns Group admin.
|
||||
*/
|
||||
async getGroupAdmin(groupId: string): Promise<string> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const groupAdminUpdatedEvents = await getEvents(
|
||||
this._contract,
|
||||
"GroupAdminUpdated",
|
||||
[groupId],
|
||||
this._options.startBlock
|
||||
)
|
||||
|
||||
if (groupAdminUpdatedEvents.length === 0) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
return groupAdminUpdatedEvents[groupAdminUpdatedEvents.length - 1].newAdmin.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of group members.
|
||||
* @param groupId Group id.
|
||||
* @returns Group members.
|
||||
*/
|
||||
async getGroupMembers(groupId: string): Promise<string[]> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
|
||||
|
||||
if (!groupCreatedEvent) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
const zeroValue = groupCreatedEvent.zeroValue.toString()
|
||||
const memberRemovedEvents = await getEvents(
|
||||
this._contract,
|
||||
"MemberRemoved",
|
||||
[groupId],
|
||||
this._options.startBlock
|
||||
)
|
||||
const memberUpdatedEvents = await getEvents(
|
||||
this._contract,
|
||||
"MemberUpdated",
|
||||
[groupId],
|
||||
this._options.startBlock
|
||||
)
|
||||
const groupUpdates = new Map<string, [number, string]>()
|
||||
|
||||
for (const { blockNumber, index, newIdentityCommitment } of memberUpdatedEvents) {
|
||||
groupUpdates.set(index.toString(), [blockNumber, newIdentityCommitment.toString()])
|
||||
}
|
||||
|
||||
for (const { blockNumber, index } of memberRemovedEvents) {
|
||||
const groupUpdate = groupUpdates.get(index.toString())
|
||||
|
||||
if (!groupUpdate || (groupUpdate && groupUpdate[0] < blockNumber)) {
|
||||
groupUpdates.set(index.toString(), [blockNumber, zeroValue])
|
||||
}
|
||||
}
|
||||
|
||||
const memberAddedEvents = await getEvents(this._contract, "MemberAdded", [groupId], this._options.startBlock)
|
||||
const members: string[] = []
|
||||
|
||||
for (const { index, identityCommitment } of memberAddedEvents) {
|
||||
const groupUpdate = groupUpdates.get(index.toString())
|
||||
const member = groupUpdate ? groupUpdate[1].toString() : identityCommitment.toString()
|
||||
|
||||
members.push(member)
|
||||
}
|
||||
|
||||
return members
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of group verified proofs.
|
||||
* @param groupId Group id.
|
||||
* @returns Group verified proofs.
|
||||
*/
|
||||
async getGroupVerifiedProofs(groupId: string): Promise<any> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
|
||||
const [groupCreatedEvent] = await getEvents(this._contract, "GroupCreated", [groupId], this._options.startBlock)
|
||||
|
||||
if (!groupCreatedEvent) {
|
||||
throw new Error(`Group '${groupId}' not found`)
|
||||
}
|
||||
|
||||
const proofVerifiedEvents = await getEvents(
|
||||
this._contract,
|
||||
"ProofVerified",
|
||||
[groupId],
|
||||
this._options.startBlock
|
||||
)
|
||||
|
||||
return proofVerifiedEvents.map((event) => ({
|
||||
signal: event.signal.toString(),
|
||||
merkleTreeRoot: event.merkleTreeRoot.toString(),
|
||||
externalNullifier: event.externalNullifier.toString(),
|
||||
nullifierHash: event.externalNullifier.toString()
|
||||
}))
|
||||
}
|
||||
}
|
||||
22
packages/data/src/getEvents.ts
Normal file
22
packages/data/src/getEvents.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/* istanbul ignore file */
|
||||
import { Contract } from "@ethersproject/contracts"
|
||||
|
||||
/**
|
||||
* Returns the list of events of a contract with possible filters.
|
||||
* @param contract Contract instance.
|
||||
* @param eventName Name of the event.
|
||||
* @param filterArgs Filter arguments.
|
||||
* @param startBlock Block from which to start fetching.
|
||||
* @returns List of contract events.
|
||||
*/
|
||||
export default async function getEvents(
|
||||
contract: Contract,
|
||||
eventName: string,
|
||||
filterArgs: any[] = [],
|
||||
startBlock: number = 0
|
||||
): Promise<any[]> {
|
||||
const filter = contract.filters[eventName](...filterArgs)
|
||||
const events = await contract.queryFilter(filter, startBlock)
|
||||
|
||||
return events.map(({ args, blockNumber }) => ({ ...args, blockNumber }))
|
||||
}
|
||||
@@ -8,9 +8,10 @@ import { Network } from "./types"
|
||||
export default function getURL(network: Network): string {
|
||||
switch (network) {
|
||||
case "goerli":
|
||||
return `https://api.thegraph.com/subgraphs/name/semaphore-protocol/goerli-5259d3`
|
||||
case "mumbai":
|
||||
case "optimism-goerli":
|
||||
case "arbitrum":
|
||||
return `https://api.thegraph.com/subgraphs/name/semaphore-protocol/arbitrum-86337c`
|
||||
return `https://api.studio.thegraph.com/query/14377/semaphore-${network}/v3.2.0`
|
||||
default:
|
||||
throw new TypeError(`Network '${network}' is not supported`)
|
||||
}
|
||||
5
packages/data/src/index.ts
Normal file
5
packages/data/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import SemaphoreSubgraph from "./subgraph"
|
||||
import SemaphoreEthers from "./ethers"
|
||||
|
||||
export { SemaphoreSubgraph, SemaphoreEthers }
|
||||
export * from "./types"
|
||||
@@ -8,7 +8,13 @@ import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
export default async function request(url: string, config?: AxiosRequestConfig): Promise<any> {
|
||||
const { data }: AxiosResponse<any> = await axios(url, config)
|
||||
const { data }: AxiosResponse<any> = await axios(url, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...config?.headers
|
||||
},
|
||||
...config
|
||||
})
|
||||
|
||||
return data?.data
|
||||
}
|
||||
575
packages/data/src/semaphoreABI.json
Normal file
575
packages/data/src/semaphoreABI.json
Normal file
@@ -0,0 +1,575 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "contract ISemaphoreVerifier",
|
||||
"name": "_verifier",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__CallerIsNotTheGroupAdmin",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__GroupAlreadyExists",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__GroupDoesNotExist",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__MerkleTreeDepthIsNotSupported",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__MerkleTreeRootIsExpired",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__MerkleTreeRootIsNotPartOfTheGroup",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "Semaphore__YouAreUsingTheSameNillifierTwice",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "oldAdmin",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newAdmin",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "GroupAdminUpdated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDepth",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "zeroValue",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "GroupCreated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "oldMerkleTreeDuration",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "newMerkleTreeDuration",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "GroupMerkleTreeDurationUpdated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "MemberAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "MemberRemoved",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "index",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "newIdentityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "MemberUpdated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint256",
|
||||
"name": "externalNullifier",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "nullifierHash",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "signal",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ProofVerified",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "addMember",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "identityCommitments",
|
||||
"type": "uint256[]"
|
||||
}
|
||||
],
|
||||
"name": "addMembers",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDepth",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "admin",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDuration",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "createGroup",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDepth",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "admin",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "createGroup",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getMerkleTreeDepth",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getMerkleTreeRoot",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getNumberOfMerkleTreeLeaves",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "groups",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "admin",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeDuration",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "proofSiblings",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8[]",
|
||||
"name": "proofPathIndices",
|
||||
"type": "uint8[]"
|
||||
}
|
||||
],
|
||||
"name": "removeMember",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newAdmin",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "updateGroupAdmin",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "newMerkleTreeDuration",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "updateGroupMerkleTreeDuration",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "identityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "newIdentityCommitment",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "proofSiblings",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8[]",
|
||||
"name": "proofPathIndices",
|
||||
"type": "uint8[]"
|
||||
}
|
||||
],
|
||||
"name": "updateMember",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "verifier",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract ISemaphoreVerifier",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "groupId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "merkleTreeRoot",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "signal",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "nullifierHash",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "externalNullifier",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[8]",
|
||||
"name": "proof",
|
||||
"type": "uint256[8]"
|
||||
}
|
||||
],
|
||||
"name": "verifyProof",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
@@ -1,5 +1,5 @@
|
||||
import request from "./request"
|
||||
import Subgraph from "./subgraph"
|
||||
import SemaphoreSubgraph from "./subgraph"
|
||||
|
||||
jest.mock("./request", () => ({
|
||||
__esModule: true,
|
||||
@@ -8,28 +8,58 @@ jest.mock("./request", () => ({
|
||||
|
||||
const requestMocked = request as jest.MockedFunction<typeof request>
|
||||
|
||||
describe("Subgraph", () => {
|
||||
let subgraph: Subgraph
|
||||
describe("SemaphoreSubgraph", () => {
|
||||
let semaphore: SemaphoreSubgraph
|
||||
|
||||
describe("# Subgraph", () => {
|
||||
it("Should instantiate a subgraph object", () => {
|
||||
subgraph = new Subgraph("goerli")
|
||||
const subgraph1 = new Subgraph()
|
||||
describe("# SemaphoreSubgraph", () => {
|
||||
it("Should instantiate a SemaphoreSubgraph object", () => {
|
||||
semaphore = new SemaphoreSubgraph()
|
||||
const semaphore1 = new SemaphoreSubgraph("arbitrum")
|
||||
|
||||
expect(subgraph.url).toContain("goerli")
|
||||
expect(subgraph1.url).toContain("arbitrum")
|
||||
expect(semaphore.url).toContain("goerli")
|
||||
expect(semaphore1.url).toContain("arbitrum")
|
||||
})
|
||||
|
||||
it("Should instantiate a SemaphoreSubgraph object using URL", () => {
|
||||
const url = "https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.2.0"
|
||||
const semaphore1 = new SemaphoreSubgraph(url)
|
||||
|
||||
expect(semaphore1.url).toBe(url)
|
||||
})
|
||||
|
||||
it("Should throw an error if there is a wrong network", () => {
|
||||
const fun = () => new Subgraph("wrong" as any)
|
||||
const fun = () => new SemaphoreSubgraph("wrong" as any)
|
||||
|
||||
expect(fun).toThrow("Network 'wrong' is not supported")
|
||||
})
|
||||
|
||||
it("Should throw an error if the network parameter type is wrong", () => {
|
||||
const fun = () => new Subgraph(33 as any)
|
||||
it("Should throw an error if the networkOrSubgraphURL parameter type is wrong", () => {
|
||||
const fun = () => new SemaphoreSubgraph(33 as any)
|
||||
|
||||
expect(fun).toThrow("Parameter 'network' is not a string")
|
||||
expect(fun).toThrow("Parameter 'networkOrSubgraphURL' is not a string")
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroupIds", () => {
|
||||
it("Should return all the existing group ids", async () => {
|
||||
requestMocked.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
groups: [
|
||||
{
|
||||
id: "1"
|
||||
},
|
||||
{
|
||||
id: "2"
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await semaphore.getGroupIds()
|
||||
|
||||
expect(expectedValue).toBeDefined()
|
||||
expect(Array.isArray(expectedValue)).toBeTruthy()
|
||||
expect(expectedValue).toContainEqual("1")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,7 +82,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await subgraph.getGroups()
|
||||
const expectedValue = await semaphore.getGroups()
|
||||
|
||||
expect(expectedValue).toBeDefined()
|
||||
expect(Array.isArray(expectedValue)).toBeTruthy()
|
||||
@@ -69,7 +99,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
|
||||
it("Should throw an error if the options parameter type is wrong", async () => {
|
||||
const fun = () => subgraph.getGroups(1 as any)
|
||||
const fun = () => semaphore.getGroups(1 as any)
|
||||
|
||||
await expect(fun).rejects.toThrow("Parameter 'options' is not an object")
|
||||
})
|
||||
@@ -116,7 +146,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await subgraph.getGroups({
|
||||
const expectedValue = await semaphore.getGroups({
|
||||
members: true,
|
||||
verifiedProofs: true
|
||||
})
|
||||
@@ -151,6 +181,44 @@ describe("Subgraph", () => {
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it("Should return groups by applying filters", async () => {
|
||||
requestMocked.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
groups: [
|
||||
{
|
||||
id: "1",
|
||||
merkleTree: {
|
||||
depth: 20,
|
||||
zeroValue: 0,
|
||||
numberOfLeaves: 2,
|
||||
root: "2"
|
||||
},
|
||||
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await semaphore.getGroups({
|
||||
filters: {
|
||||
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
|
||||
}
|
||||
})
|
||||
|
||||
expect(expectedValue).toBeDefined()
|
||||
expect(Array.isArray(expectedValue)).toBeTruthy()
|
||||
expect(expectedValue).toContainEqual({
|
||||
id: "1",
|
||||
merkleTree: {
|
||||
depth: 20,
|
||||
zeroValue: 0,
|
||||
numberOfLeaves: 2,
|
||||
root: "2"
|
||||
},
|
||||
admin: "0x7bcd6f009471e9974a77086a69289d16eadba286"
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("# getGroup", () => {
|
||||
@@ -172,7 +240,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await subgraph.getGroup("1")
|
||||
const expectedValue = await semaphore.getGroup("1")
|
||||
|
||||
expect(expectedValue).toBeDefined()
|
||||
expect(expectedValue).toEqual({
|
||||
@@ -188,7 +256,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
|
||||
it("Should throw an error if the options parameter type is wrong", async () => {
|
||||
const fun = () => subgraph.getGroup("1", 1 as any)
|
||||
const fun = () => semaphore.getGroup("1", 1 as any)
|
||||
|
||||
await expect(fun).rejects.toThrow("Parameter 'options' is not an object")
|
||||
})
|
||||
@@ -235,7 +303,7 @@ describe("Subgraph", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const expectedValue = await subgraph.getGroup("1", {
|
||||
const expectedValue = await semaphore.getGroup("1", {
|
||||
members: true,
|
||||
verifiedProofs: true
|
||||
})
|
||||
@@ -2,19 +2,25 @@ import { AxiosRequestConfig } from "axios"
|
||||
import checkParameter from "./checkParameter"
|
||||
import getURL from "./getURL"
|
||||
import request from "./request"
|
||||
import { GroupOptions, Network } from "./types"
|
||||
import { GroupResponse, GroupOptions, Network } from "./types"
|
||||
import { jsDateToGraphqlDate } from "./utils"
|
||||
|
||||
export default class Subgraph {
|
||||
export default class SemaphoreSubgraph {
|
||||
private _url: string
|
||||
|
||||
/**
|
||||
* Initializes the subgraph object with one of the supported networks.
|
||||
* @param network Supported Semaphore network.
|
||||
* Initializes the subgraph object with one of the supported networks or a custom URL.
|
||||
* @param networkOrSubgraphURL Supported Semaphore network or custom Subgraph URL.
|
||||
*/
|
||||
constructor(network: Network = "arbitrum") {
|
||||
checkParameter(network, "network", "string")
|
||||
constructor(networkOrSubgraphURL: Network | string = "goerli") {
|
||||
checkParameter(networkOrSubgraphURL, "networkOrSubgraphURL", "string")
|
||||
|
||||
this._url = getURL(network)
|
||||
if (networkOrSubgraphURL.startsWith("http")) {
|
||||
this._url = networkOrSubgraphURL
|
||||
return
|
||||
}
|
||||
|
||||
this._url = getURL(networkOrSubgraphURL as Network)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,12 +31,33 @@ export default class Subgraph {
|
||||
return this._url
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of group ids.
|
||||
* @returns List of group ids.
|
||||
*/
|
||||
async getGroupIds(): Promise<string[]> {
|
||||
const config: AxiosRequestConfig = {
|
||||
method: "post",
|
||||
data: JSON.stringify({
|
||||
query: `{
|
||||
groups {
|
||||
id
|
||||
}
|
||||
}`
|
||||
})
|
||||
}
|
||||
|
||||
const { groups } = await request(this._url, config)
|
||||
|
||||
return groups.map((group: any) => group.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of groups.
|
||||
* @param options Options to select the group parameters.
|
||||
* @returns List of groups.
|
||||
*/
|
||||
async getGroups(options: GroupOptions = {}): Promise<any[]> {
|
||||
async getGroups(options: GroupOptions = {}): Promise<GroupResponse[]> {
|
||||
checkParameter(options, "options", "object")
|
||||
|
||||
const { members = false, verifiedProofs = false } = options
|
||||
@@ -38,11 +65,35 @@ export default class Subgraph {
|
||||
checkParameter(members, "members", "boolean")
|
||||
checkParameter(verifiedProofs, "verifiedProofs", "boolean")
|
||||
|
||||
let filtersQuery = ""
|
||||
|
||||
if (options.filters) {
|
||||
const { admin, timestamp, timestampGte, timestampLte } = options.filters
|
||||
const filterFragments = []
|
||||
|
||||
if (admin) {
|
||||
filterFragments.push(`admin: "${admin}"`)
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (timestamp) {
|
||||
filterFragments.push(`timestamp: "${jsDateToGraphqlDate(timestamp)}"`)
|
||||
} else if (timestampGte) {
|
||||
filterFragments.push(`timestamp_gte: "${jsDateToGraphqlDate(timestampGte)}"`)
|
||||
} else if (timestampLte) {
|
||||
filterFragments.push(`timestamp_lte: "${jsDateToGraphqlDate(timestampLte)}"`)
|
||||
}
|
||||
|
||||
if (filterFragments.length > 0) {
|
||||
filtersQuery = `(where: {${filterFragments.join(", ")}})`
|
||||
}
|
||||
}
|
||||
|
||||
const config: AxiosRequestConfig = {
|
||||
method: "post",
|
||||
data: JSON.stringify({
|
||||
query: `{
|
||||
groups {
|
||||
groups ${filtersQuery} {
|
||||
id
|
||||
merkleTree {
|
||||
root
|
||||
@@ -91,7 +142,7 @@ export default class Subgraph {
|
||||
* @param options Options to select the group parameters.
|
||||
* @returns Specific group.
|
||||
*/
|
||||
async getGroup(groupId: string, options: GroupOptions = {}): Promise<any> {
|
||||
async getGroup(groupId: string, options: Omit<GroupOptions, "filters"> = {}): Promise<GroupResponse> {
|
||||
checkParameter(groupId, "groupId", "string")
|
||||
checkParameter(options, "options", "object")
|
||||
|
||||
48
packages/data/src/types/index.ts
Normal file
48
packages/data/src/types/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
export type Network =
|
||||
| "homestead"
|
||||
| "matic"
|
||||
| "goerli"
|
||||
| "arbitrum"
|
||||
| "maticmum"
|
||||
| "mumbai"
|
||||
| "arbitrum-goerli"
|
||||
| "optimism"
|
||||
| "optimism-goerli"
|
||||
| "sepolia"
|
||||
|
||||
export type GroupOptions = {
|
||||
members?: boolean
|
||||
verifiedProofs?: boolean
|
||||
filters?: {
|
||||
admin?: string
|
||||
timestamp?: Date
|
||||
timestampGte?: Date
|
||||
timestampLte?: Date
|
||||
}
|
||||
}
|
||||
|
||||
export type GroupResponse = {
|
||||
id: string
|
||||
merkleTree: {
|
||||
root: string
|
||||
depth: number
|
||||
zeroValue: string
|
||||
numberOfLeaves: number
|
||||
}
|
||||
admin?: string
|
||||
members?: string[]
|
||||
verifiedProofs?: {
|
||||
signal: string
|
||||
merkleTreeRoot: string
|
||||
externalNullifier: string
|
||||
nullifierHash: string
|
||||
timestamp?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export type EthersOptions = {
|
||||
address?: string
|
||||
startBlock?: number
|
||||
provider?: "etherscan" | "infura" | "alchemy" | "cloudflare" | "pocket" | "ankr"
|
||||
apiKey?: string
|
||||
}
|
||||
11
packages/data/src/utils.test.ts
Normal file
11
packages/data/src/utils.test.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { jsDateToGraphqlDate } from "./utils"
|
||||
|
||||
describe("Utils", () => {
|
||||
describe("# jsDateToGraphqlDate", () => {
|
||||
it("Should convert a JS date to the GraphQL date", async () => {
|
||||
const date = jsDateToGraphqlDate(new Date("2020-01-01"))
|
||||
|
||||
expect(date).toBe(1577836800)
|
||||
})
|
||||
})
|
||||
})
|
||||
4
packages/data/src/utils.ts
Normal file
4
packages/data/src/utils.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function jsDateToGraphqlDate(date: Date): number {
|
||||
return Math.round(date.getTime() / 1000)
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/group",
|
||||
"version": "2.6.1",
|
||||
"version": "3.2.3",
|
||||
"description": "A library to create and manage Semaphore groups.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -30,6 +30,9 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"poseidon-lite": "^0.1.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
@@ -38,7 +41,6 @@
|
||||
"@ethersproject/bignumber": "^5.7.0",
|
||||
"@ethersproject/bytes": "^5.7.0",
|
||||
"@ethersproject/keccak256": "^5.7.0",
|
||||
"@zk-kit/incremental-merkle-tree": "1.0.0",
|
||||
"poseidon-lite": "^0.0.2"
|
||||
"@zk-kit/incremental-merkle-tree": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" })
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type Member = string | number | bigint
|
||||
export type BigNumberish = string | number | bigint
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<h1 align="center">
|
||||
Semaphore Hardhat plugin
|
||||
</h1>
|
||||
<p align="center">A Semaphore Hardhat plugin to deploy verifiers and Semaphore contracts.</p>
|
||||
<p align="center">A Semaphore Hardhat plugin to deploy Semaphore contracts.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -40,14 +40,14 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This Hardhat plugin provides two simple tasks that can be used to deploy verifiers and Semaphore contracts without any additional configuration. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| This Hardhat plugin provides two simple tasks that can be used to deploy Semaphore contracts without any additional configuration. |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
@@ -88,21 +88,18 @@ import { task, types } from "hardhat/config"
|
||||
task("deploy", "Deploy a Greeter contract")
|
||||
.addOptionalParam("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs }, { ethers, run }) => {
|
||||
const { address: verifierAddress } = await run("deploy:verifier", { logs, merkleTreeDepth: 20 })
|
||||
|
||||
const { address: semaphoreAddress } = await run("deploy:semaphore", {
|
||||
logs,
|
||||
verifiers: [
|
||||
{
|
||||
merkleTreeDepth: 20,
|
||||
contractAddress: verifierAddress
|
||||
}
|
||||
]
|
||||
const { semaphore } = await run("deploy:semaphore", {
|
||||
logs
|
||||
})
|
||||
|
||||
// Or:
|
||||
// const { semaphoreVerifier } = await run("deploy:semaphore-verifier", {
|
||||
// logs
|
||||
// })
|
||||
|
||||
const Greeter = await ethers.getContractFactory("Greeter")
|
||||
|
||||
const greeter = await Greeter.deploy(semaphoreAddress)
|
||||
const greeter = await Greeter.deploy(semaphore.address)
|
||||
|
||||
await greeter.deployed()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/hardhat",
|
||||
"version": "0.1.0",
|
||||
"version": "3.2.3",
|
||||
"description": "A Semaphore Hardhat plugin to deploy verifiers and Semaphore contract.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -38,7 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nomiclabs/hardhat-ethers": "^2.1.1",
|
||||
"@semaphore-protocol/contracts": "^2.5.0",
|
||||
"@semaphore-protocol/contracts": "3.2.3",
|
||||
"circomlibjs": "^0.0.8",
|
||||
"ethers": "^5.7.1",
|
||||
"hardhat-dependency-compiler": "^1.1.3"
|
||||
|
||||
@@ -4,27 +4,12 @@ import { HardhatConfig, HardhatUserConfig } from "hardhat/types"
|
||||
import "hardhat-dependency-compiler"
|
||||
import "@nomiclabs/hardhat-ethers"
|
||||
import "./tasks/deploy-semaphore"
|
||||
import "./tasks/deploy-verifier"
|
||||
import "./tasks/deploy-semaphore-verifier"
|
||||
|
||||
extendConfig((config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
|
||||
config.dependencyCompiler.paths = [
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier16.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier17.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier18.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier19.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier20.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier21.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier22.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier23.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier24.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier25.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier26.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier27.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier28.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier29.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier30.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier31.sol",
|
||||
"@semaphore-protocol/contracts/verifiers/Verifier32.sol",
|
||||
"@semaphore-protocol/contracts/base/Pairing.sol",
|
||||
"@semaphore-protocol/contracts/base/SemaphoreVerifier.sol",
|
||||
"@semaphore-protocol/contracts/Semaphore.sol"
|
||||
]
|
||||
|
||||
|
||||
38
packages/hardhat/src/tasks/deploy-semaphore-verifier.ts
Normal file
38
packages/hardhat/src/tasks/deploy-semaphore-verifier.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore-verifier", "Deploy a SemaphoreVerifier contract")
|
||||
.addOptionalParam<boolean>("pairing", "Pairing library address", undefined, types.string)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, pairing: pairingAddress }, { ethers }): Promise<any> => {
|
||||
if (!pairingAddress) {
|
||||
const PairingFactory = await ethers.getContractFactory("Pairing")
|
||||
const pairing = await PairingFactory.deploy()
|
||||
|
||||
await pairing.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Pairing library has been deployed to: ${pairing.address}`)
|
||||
}
|
||||
|
||||
pairingAddress = pairing.address
|
||||
}
|
||||
|
||||
const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", {
|
||||
libraries: {
|
||||
Pairing: pairingAddress
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreVerifier = await SemaphoreVerifierFactory.deploy()
|
||||
|
||||
await semaphoreVerifier.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`)
|
||||
}
|
||||
|
||||
return {
|
||||
semaphoreVerifier,
|
||||
pairingAddress
|
||||
}
|
||||
})
|
||||
@@ -1,51 +1,114 @@
|
||||
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:semaphore", "Deploy a Semaphore contract")
|
||||
.addParam("verifiers", "Tree depths and verifier addresses", [], types.json)
|
||||
.addOptionalParam<boolean>("pairing", "Pairing library address", undefined, types.string)
|
||||
.addOptionalParam<boolean>("semaphoreVerifier", "SemaphoreVerifier contract address", undefined, types.string)
|
||||
.addOptionalParam<boolean>("poseidon", "Poseidon library address", undefined, types.string)
|
||||
.addOptionalParam<boolean>(
|
||||
"incrementalBinaryTree",
|
||||
"IncrementalBinaryTree library address",
|
||||
undefined,
|
||||
types.string
|
||||
)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ logs, verifiers }, { ethers }): Promise<Contract> => {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
.setAction(
|
||||
async (
|
||||
{
|
||||
logs,
|
||||
pairing: pairingAddress,
|
||||
semaphoreVerifier: semaphoreVerifierAddress,
|
||||
poseidon: poseidonAddress,
|
||||
incrementalBinaryTree: incrementalBinaryTreeAddress
|
||||
},
|
||||
{ ethers }
|
||||
): Promise<any> => {
|
||||
if (!semaphoreVerifierAddress) {
|
||||
if (!pairingAddress) {
|
||||
const PairingFactory = await ethers.getContractFactory("Pairing")
|
||||
const pairing = await PairingFactory.deploy()
|
||||
|
||||
const [signer] = await ethers.getSigners()
|
||||
await pairing.deployed()
|
||||
|
||||
const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer)
|
||||
const poseidonLib = await PoseidonLibFactory.deploy()
|
||||
if (logs) {
|
||||
console.info(`Pairing library has been deployed to: ${pairing.address}`)
|
||||
}
|
||||
|
||||
await poseidonLib.deployed()
|
||||
pairingAddress = pairing.address
|
||||
}
|
||||
|
||||
if (logs) {
|
||||
console.info(`Poseidon library has been deployed to: ${poseidonLib.address}`)
|
||||
}
|
||||
const SemaphoreVerifierFactory = await ethers.getContractFactory("SemaphoreVerifier", {
|
||||
libraries: {
|
||||
Pairing: pairingAddress
|
||||
}
|
||||
})
|
||||
|
||||
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
|
||||
libraries: {
|
||||
PoseidonT3: poseidonLib.address
|
||||
const semaphoreVerifier = await SemaphoreVerifierFactory.deploy()
|
||||
|
||||
await semaphoreVerifier.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`SemaphoreVerifier contract has been deployed to: ${semaphoreVerifier.address}`)
|
||||
}
|
||||
|
||||
semaphoreVerifierAddress = semaphoreVerifier.address
|
||||
}
|
||||
})
|
||||
const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy()
|
||||
|
||||
await incrementalBinaryTreeLib.deployed()
|
||||
if (!incrementalBinaryTreeAddress) {
|
||||
if (!poseidonAddress) {
|
||||
const poseidonABI = poseidonContract.generateABI(2)
|
||||
const poseidonBytecode = poseidonContract.createCode(2)
|
||||
|
||||
if (logs) {
|
||||
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
|
||||
}
|
||||
const [signer] = await ethers.getSigners()
|
||||
|
||||
const SemaphoreContractFactory = await ethers.getContractFactory("Semaphore", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTreeLib.address
|
||||
const PoseidonFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer)
|
||||
const poseidon = await PoseidonFactory.deploy()
|
||||
|
||||
await poseidon.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Poseidon library has been deployed to: ${poseidon.address}`)
|
||||
}
|
||||
|
||||
poseidonAddress = poseidon.address
|
||||
}
|
||||
|
||||
const IncrementalBinaryTreeFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
|
||||
libraries: {
|
||||
PoseidonT3: poseidonAddress
|
||||
}
|
||||
})
|
||||
const incrementalBinaryTree = await IncrementalBinaryTreeFactory.deploy()
|
||||
|
||||
await incrementalBinaryTree.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTree.address}`)
|
||||
}
|
||||
|
||||
incrementalBinaryTreeAddress = incrementalBinaryTree.address
|
||||
}
|
||||
})
|
||||
|
||||
const semaphoreContract = await SemaphoreContractFactory.deploy(verifiers)
|
||||
const SemaphoreFactory = await ethers.getContractFactory("Semaphore", {
|
||||
libraries: {
|
||||
IncrementalBinaryTree: incrementalBinaryTreeAddress
|
||||
}
|
||||
})
|
||||
|
||||
await semaphoreContract.deployed()
|
||||
const semaphore = await SemaphoreFactory.deploy(semaphoreVerifierAddress)
|
||||
|
||||
if (logs) {
|
||||
console.info(`Semaphore contract has been deployed to: ${semaphoreContract.address}`)
|
||||
await semaphore.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Semaphore contract has been deployed to: ${semaphore.address}`)
|
||||
}
|
||||
|
||||
return {
|
||||
semaphore,
|
||||
pairingAddress,
|
||||
semaphoreVerifierAddress,
|
||||
poseidonAddress,
|
||||
incrementalBinaryTreeAddress
|
||||
}
|
||||
}
|
||||
|
||||
return semaphoreContract
|
||||
})
|
||||
)
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Contract } from "ethers"
|
||||
import { task, types } from "hardhat/config"
|
||||
|
||||
task("deploy:verifier", "Deploy a Verifier contract")
|
||||
.addParam<number>("merkleTreeDepth", "Merkle tree depth", undefined, types.int)
|
||||
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
|
||||
.setAction(async ({ merkleTreeDepth, logs }, { ethers }): Promise<Contract> => {
|
||||
const VerifierContractFactory = await ethers.getContractFactory(`Verifier${merkleTreeDepth}`)
|
||||
|
||||
const verifierContract = await VerifierContractFactory.deploy()
|
||||
|
||||
await verifierContract.deployed()
|
||||
|
||||
if (logs) {
|
||||
console.info(`Verifier${merkleTreeDepth} contract has been deployed to: ${verifierContract.address}`)
|
||||
}
|
||||
|
||||
return verifierContract
|
||||
})
|
||||
@@ -40,7 +40,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/identity",
|
||||
"version": "2.6.1",
|
||||
"version": "3.2.3",
|
||||
"description": "A library to create Semaphore identities.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -30,6 +30,9 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"poseidon-lite": "^0.1.0",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.31.2",
|
||||
"typedoc": "^0.22.11"
|
||||
@@ -39,6 +42,6 @@
|
||||
"@ethersproject/keccak256": "^5.7.0",
|
||||
"@ethersproject/random": "^5.5.1",
|
||||
"@ethersproject/strings": "^5.6.1",
|
||||
"poseidon-lite": "^0.0.2"
|
||||
"js-sha512": "^0.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" })
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -45,7 +45,7 @@ describe("Identity", () => {
|
||||
it("Should not recreate an existing invalid identity", () => {
|
||||
const fun = () => new Identity('[true, "01323"]')
|
||||
|
||||
expect(fun).toThrow("invalid BigNumber string")
|
||||
expect(fun).toThrow("invalid BigNumber value")
|
||||
})
|
||||
|
||||
it("Should recreate an existing identity", () => {
|
||||
@@ -64,7 +64,9 @@ describe("Identity", () => {
|
||||
|
||||
const trapdoor = identity.getTrapdoor()
|
||||
|
||||
expect(trapdoor).toBe(BigInt("211007102311354422986775462856672883657031335757695461477990303178796954863"))
|
||||
expect(trapdoor.toString()).toBe(
|
||||
"11566083507498623434013707198824105161167204201250008419741119866456392774309"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -74,7 +76,9 @@ describe("Identity", () => {
|
||||
|
||||
const nullifier = identity.getNullifier()
|
||||
|
||||
expect(nullifier).toBe(BigInt("10282208199720122340759039255952223220417076359839127631923809108800013776"))
|
||||
expect(nullifier.toString()).toBe(
|
||||
"14070056666392584007908120012103355272369511035580155843212703537125048345255"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -82,8 +86,8 @@ describe("Identity", () => {
|
||||
it("Should generate an identity commitment", () => {
|
||||
const { commitment } = new Identity("message")
|
||||
|
||||
expect(commitment).toBe(
|
||||
BigInt("13192222509545780880434144549342414064490325100975031303723930089730328393905")
|
||||
expect(commitment.toString()).toBe(
|
||||
"19361462367798001240039467285882167157718016385695743307694056771074972404368"
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -102,8 +106,8 @@ describe("Identity", () => {
|
||||
|
||||
const [trapdoor, nullifier] = JSON.parse(identity.toString())
|
||||
|
||||
expect(BigNumber.from(`0x${trapdoor}`).toBigInt()).toBe(identity.trapdoor)
|
||||
expect(BigNumber.from(`0x${nullifier}`).toBigInt()).toBe(identity.nullifier)
|
||||
expect(BigNumber.from(trapdoor).toBigInt()).toBe(identity.trapdoor)
|
||||
expect(BigNumber.from(nullifier).toBigInt()).toBe(identity.nullifier)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BigNumber } from "@ethersproject/bignumber"
|
||||
import hash from "js-sha512"
|
||||
import checkParameter from "./checkParameter"
|
||||
import hash from "./hash"
|
||||
import { generateCommitment, genRandomNumber, isJsonArray } from "./utils"
|
||||
|
||||
export default class Identity {
|
||||
@@ -24,10 +24,10 @@ export default class Identity {
|
||||
checkParameter(identityOrMessage, "identityOrMessage", "string")
|
||||
|
||||
if (!isJsonArray(identityOrMessage)) {
|
||||
const messageHash = hash(identityOrMessage)
|
||||
|
||||
this._trapdoor = hash(`${messageHash}identity_trapdoor`)
|
||||
this._nullifier = hash(`${messageHash}identity_nullifier`)
|
||||
const h = hash.sha512(identityOrMessage).padStart(128, "0")
|
||||
// alt_bn128 is 253.6 bits, so we can safely use 253 bits
|
||||
this._trapdoor = BigInt(`0x${h.slice(64)}`) >> BigInt(3)
|
||||
this._nullifier = BigInt(`0x${h.slice(0, 64)}`) >> BigInt(3)
|
||||
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
|
||||
|
||||
return
|
||||
@@ -35,8 +35,8 @@ export default class Identity {
|
||||
|
||||
const [trapdoor, nullifier] = JSON.parse(identityOrMessage)
|
||||
|
||||
this._trapdoor = BigNumber.from(`0x${trapdoor}`).toBigInt()
|
||||
this._nullifier = BigNumber.from(`0x${nullifier}`).toBigInt()
|
||||
this._trapdoor = BigNumber.from(trapdoor).toBigInt()
|
||||
this._nullifier = BigNumber.from(nullifier).toBigInt()
|
||||
this._commitment = generateCommitment(this._nullifier, this._trapdoor)
|
||||
}
|
||||
|
||||
@@ -94,6 +94,6 @@ export default class Identity {
|
||||
* @returns The string representation of the identity.
|
||||
*/
|
||||
public toString(): string {
|
||||
return JSON.stringify([this._trapdoor.toString(16), this._nullifier.toString(16)])
|
||||
return JSON.stringify([`0x${this._trapdoor.toString(16)}`, `0x${this._nullifier.toString(16)}`])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BigNumber } from "@ethersproject/bignumber"
|
||||
import { randomBytes } from "@ethersproject/random"
|
||||
import poseidon from "poseidon-lite"
|
||||
import { poseidon1 } from "poseidon-lite/poseidon1"
|
||||
import { poseidon2 } from "poseidon-lite/poseidon2"
|
||||
|
||||
/**
|
||||
* Generates a random big number.
|
||||
@@ -18,7 +19,7 @@ export function genRandomNumber(numberOfBytes = 31): bigint {
|
||||
* @returns identity commitment
|
||||
*/
|
||||
export function generateCommitment(nullifier: bigint, trapdoor: bigint): bigint {
|
||||
return poseidon([poseidon([nullifier, trapdoor])])
|
||||
return poseidon1([poseidon2([nullifier, trapdoor])])
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
<a href="https://semaphore.appliedzkp.org/discord">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/proof",
|
||||
"version": "2.6.1",
|
||||
"version": "3.2.3",
|
||||
"description": "A library to generate and verify Semaphore proofs.",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.node.js",
|
||||
@@ -37,8 +37,8 @@
|
||||
"typedoc": "^0.22.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@semaphore-protocol/group": "2.6.1",
|
||||
"@semaphore-protocol/identity": "2.6.1"
|
||||
"@semaphore-protocol/group": "3.2.3",
|
||||
"@semaphore-protocol/identity": "3.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersproject/bignumber": "^5.5.0",
|
||||
@@ -46,6 +46,6 @@
|
||||
"@ethersproject/keccak256": "^5.7.0",
|
||||
"@ethersproject/strings": "^5.5.0",
|
||||
"@zk-kit/incremental-merkle-tree": "0.4.3",
|
||||
"snarkjs": "^0.4.13"
|
||||
"snarkjs": "0.4.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
19
packages/proof/src/packProof.ts
Normal file
19
packages/proof/src/packProof.ts
Normal 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]
|
||||
]
|
||||
}
|
||||
@@ -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]
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
19
packages/proof/src/unpackProof.ts
Normal file
19
packages/proof/src/unpackProof.ts
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
| This library allows you to query the [`Semaphore.sol`](https://github.com/semaphore-protocol/semaphore/blob/main/contracts/Semaphore.sol) contract data (i.e. groups) using the [Semaphore subgraph](https://github.com/semaphore-protocol/subgraph) on Kovan, Goerli, and Arbitrum One. It can be used on Node.js and browsers. |
|
||||
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/subgraph` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/subgraph
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/subgraph
|
||||
```
|
||||
|
||||
### CDN
|
||||
|
||||
You can also load it using a `script` tag using [unpkg](https://unpkg.com/):
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||
<script src="https://unpkg.com/@semaphore-protocol/subgraph/"></script>
|
||||
```
|
||||
|
||||
or [JSDelivr](https://www.jsdelivr.com/):
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@semaphore-protocol/subgraph/"></script>
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
\# **new Subgraph**(network: _Network_ = "arbitrum" ): _Subgraph_
|
||||
|
||||
```typescript
|
||||
import { Subgraph } from "@semaphore-protocol/subgraph"
|
||||
|
||||
const subgraph = new Subgraph()
|
||||
```
|
||||
|
||||
\# **getGroups**(options?: _GroupOptions_)
|
||||
|
||||
```typescript
|
||||
const groups = subgraph.getGroups()
|
||||
|
||||
// or
|
||||
|
||||
const groups = subgraph.getGroups({ members: true, verifiedProofs: true })
|
||||
```
|
||||
|
||||
\# **getGroup**(groupId: _string_, options?: _GroupOptions_)
|
||||
|
||||
```typescript
|
||||
const group = subgraph.getGroup("1")
|
||||
|
||||
// or
|
||||
|
||||
const { members, verifiedProofs } = subgraph.getGroup("1", { members: true, verifiedProofs: true })
|
||||
```
|
||||
@@ -1,4 +0,0 @@
|
||||
import Subgraph from "./subgraph"
|
||||
|
||||
export { Subgraph }
|
||||
export * from "./types"
|
||||
@@ -1,6 +0,0 @@
|
||||
export type Network = "goerli" | "arbitrum"
|
||||
|
||||
export type GroupOptions = {
|
||||
members?: boolean
|
||||
verifiedProofs?: boolean
|
||||
}
|
||||
1
yarn.lock.REMOVED.git-id
Normal file
1
yarn.lock.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
5d109b5b737dadc4046717430e8fd16c727749b9
|
||||
Reference in New Issue
Block a user