Compare commits

...

61 Commits

Author SHA1 Message Date
cedoor
ac3e7b42a3 chore: v2.6.1
Former-commit-id: ec5c69a795
2022-10-26 16:31:48 +02:00
cedoor
2b414f8c24 fix: set correct thegraph api urls
Former-commit-id: 29ebbe3b09
2022-10-26 16:28:26 +02:00
cedoor
1fcff83c1a style: format code with prettier
Former-commit-id: a5f1d80d84
2022-10-26 16:16:37 +02:00
cedoor
eb2d6ee62b chore: update arbitrum contract addresses
Former-commit-id: 6a60f727b8
2022-10-26 16:14:38 +02:00
cedoor
db624c24e0 style: format code with prettier
Former-commit-id: 9564ad19f0
2022-10-24 12:28:19 +02:00
cedoor
95e5ff669b chore: update goerli contract addresses
Former-commit-id: 88f44c3f49
2022-10-24 12:13:19 +02:00
cedoor
6a5e9e32d3 chore: update lockfile
Former-commit-id: c9b8ab574c
2022-10-19 19:06:50 +02:00
cedoor
08d7621fc2 chore: v2.6.0
Former-commit-id: 4e0500f9ce
2022-10-19 19:05:55 +02:00
cedoor
ca3a4c1971 style: format code with prettier
Former-commit-id: b574e6a1e8
2022-10-19 18:44:04 +02:00
cedoor
dc0ceee50d chore(contracts): update incremental-merkle-tree dep version
Former-commit-id: be82cf1104
2022-10-19 17:03:44 +02:00
cedoor
6fadd3e745 docs: update contributing file
Former-commit-id: fd9a00fd58
2022-10-19 09:57:47 +02:00
cedoor
e86228102e chore(contracts): update openzeppelin dependency version
Former-commit-id: 9eebe0173a
2022-10-19 09:55:40 +02:00
cedoor
91f56941b4 docs: update identity pkg readme description
Former-commit-id: 93e042cf34
2022-10-15 17:51:12 -05:00
cedoor
22aadf2745 docs: update identity pkg readme file
Former-commit-id: 5eb18bfe5d
2022-10-15 17:47:31 -05:00
cedoor
8458a623e0 Merge pull request #142 from semaphore-protocol/feat/identity-getters
New identity class getters

Former-commit-id: bbcb065224
2022-10-13 15:13:53 -05:00
cedoor
e6a3699a56 feat: add identity class getters
Former-commit-id: 401d303b39
2022-09-30 11:46:21 +02:00
cedoor
ed74eff52a docs: remove docs link for hardhat pkg
Former-commit-id: 26e0cbcc9d
2022-09-22 13:54:57 +02:00
cedoor
a7e5b28b00 docs: add hardhat package
Former-commit-id: 92dd1db419
2022-09-22 13:49:16 +02:00
cedoor
08576e1717 Merge pull request #140 from semaphore-protocol/feat/hardhat-plugin
feat: create semaphore hardhat plugin
Former-commit-id: ab41ff13fb
2022-09-22 13:39:01 +02:00
cedoor
80376ab81e feat: create semaphore hardhat plugin
Former-commit-id: 6ceb226f5e
2022-09-21 16:16:42 +02:00
cedoor
32efdd952b style: format code with prettier
Former-commit-id: d4f85d7d04
2022-09-19 17:13:46 +02:00
cedoor
f6b79ba5d2 chore: update goerli contract addresses
Former-commit-id: b117b1ff00
2022-09-19 17:09:16 +02:00
cedoor
1a995d7087 chore: v2.5.0
Former-commit-id: 68779e90a0
2022-09-19 16:54:51 +02:00
cedoor
20fc3c58d7 Merge pull request #138 from semaphore-protocol/feat/custom-nullifier-hashes
Custom nullifier hashes

Former-commit-id: 506fa0ab7d
2022-09-19 16:49:19 +02:00
cedoor
7329ca6a48 feat: move nullifier hashes out of SemaphoreCore
Former-commit-id: f57e21c77d
2022-09-19 16:36:30 +02:00
cedoor
0c9c0c9791 style: fix eslint errors
Former-commit-id: 0a821d8214
2022-09-19 13:15:19 +02:00
cedoor
39a7e32143 style: format code with prettier
Former-commit-id: eba7d053e7
2022-09-19 13:08:25 +02:00
cedoor
87c27b9d03 chore: update lockfile
Former-commit-id: 0654895c9a
2022-09-19 13:04:54 +02:00
cedoor
4d07a1ede5 chore: update semaphore dependencies
Former-commit-id: bf4f6f75cb
2022-09-19 13:02:30 +02:00
cedoor
55fbd0f2ed chore: v2.4.0
Former-commit-id: 21ea7e80e7
2022-09-19 13:00:18 +02:00
cedoor
6dca198995 refactor: update subgraph queries
Former-commit-id: f0825d738c
2022-09-19 12:57:59 +02:00
cedoor
5f80aab430 fix: set correct contract names
Former-commit-id: 13ca6a6854
2022-09-19 12:17:53 +02:00
cedoor
c30fbb8d1c chore: update goerli contract addresses
Former-commit-id: 58c08b1e41
2022-09-19 12:12:35 +02:00
cedoor
be1014452e refactor: update scripts and tasks to deploy contracts
Former-commit-id: e960722b08
2022-09-19 12:12:22 +02:00
cedoor
0036da93b1 Merge pull request #136 from semaphore-protocol/feat/group-nullifier-hashes
Feat/group nullifier hashes

Former-commit-id: f8d3b0ad0f
2022-09-18 21:03:12 +02:00
cedoor
c984adef0e feat: add proof parameters to ProofVerified event
Former-commit-id: 85d9de669b
2022-09-18 18:16:10 +02:00
cedoor
b2d8667963 feat: hash nullifier hash with group id
Former-commit-id: 0979b5aadd
2022-09-18 18:10:14 +02:00
cedoor
466be38e42 docs: fix circuit link
Former-commit-id: d5794cca97
2022-09-18 16:50:09 +02:00
cedoor
adcb8e085d ci: fix production workflow
Former-commit-id: 011ab2b053
2022-09-18 16:19:42 +02:00
cedoor
e9a3770a39 Merge pull request #134 from semaphore-protocol/chore/monorepo
Monorepo

Former-commit-id: 47b6f6eb39
2022-09-18 16:14:03 +02:00
cedoor
d9a1387f2a docs: update workflow label url
Former-commit-id: 6ad31e445a
2022-09-18 16:01:27 +02:00
cedoor
a6710ad435 ci: remove job dependency
Former-commit-id: 3a75b81dad
2022-09-18 16:01:16 +02:00
cedoor
3d3a63a10d build: set peer dependencies
Former-commit-id: 343e259956
2022-09-18 15:54:37 +02:00
cedoor
0daf5b7dae chore: move env variables to the root folder
Former-commit-id: 9d3fc642d2
2022-09-18 15:50:16 +02:00
cedoor
ef4b3dd4b2 ci: remove jobs dependencies
Former-commit-id: 7458bcc23c
2022-09-17 19:06:50 +02:00
cedoor
0f2a13463a chore: extend contracts tsconfig
Former-commit-id: af6e11c564
2022-09-17 18:52:39 +02:00
cedoor
0a305c019d ci: add script to build libs before testing
Former-commit-id: de26e4da81
2022-09-17 18:51:59 +02:00
cedoor
de8c7f20ca chore: set workspace dependencies
Former-commit-id: f048f4ec4e
2022-09-17 18:51:40 +02:00
cedoor
23d7fdff3a fix: set default tree depth
Former-commit-id: c488b6f8ed
2022-09-17 18:20:40 +02:00
cedoor
21b9965f57 ci: fix command name
Former-commit-id: a9315fc111
2022-09-17 12:52:49 +02:00
cedoor
023c8bae64 chore: ignore docs folder with eslint
Former-commit-id: 14fe1d531f
2022-09-17 12:50:40 +02:00
cedoor
0d771eb9fa ci: compile contracts before linting
Former-commit-id: 77604a2d64
2022-09-17 12:50:00 +02:00
cedoor
62f775737d style: format code with prettier
Former-commit-id: 6a65d6c2bf
2022-09-17 12:45:04 +02:00
cedoor
12aacf24b3 chore: ignore docs folder with prettier
Former-commit-id: 93bdfd4b14
2022-09-17 12:44:54 +02:00
cedoor
b450dcec79 ci: update github workflows
Former-commit-id: 9244f42c1a
2022-09-17 12:40:29 +02:00
cedoor
e909e1db99 test: set default tree depth
Former-commit-id: 024c16bcd2
2022-09-17 12:40:14 +02:00
cedoor
92c9c9bcc8 docs: set relative repo links
Former-commit-id: c163ff1dcc
2022-09-16 16:59:36 +02:00
cedoor
5588256072 docs: update readme files
Former-commit-id: 1e630902bc
2022-09-16 16:56:35 +02:00
cedoor
8cf04ddb98 chore: create semaphore monorepo
Former-commit-id: a38dd20276
2022-09-16 16:50:30 +02:00
cedoor
ecca5a4ee9 style: format code with prettier
Former-commit-id: 5fa34109c6
2022-09-15 15:52:27 +02:00
cedoor
0a5ebe60df chore: update goerli semaphore address
Former-commit-id: 1c41b8edf5
2022-09-14 16:22:57 +02:00
142 changed files with 4897 additions and 18379 deletions

View File

@@ -20,6 +20,7 @@ circuits
# production
dist
build
docs
# misc
.DS_Store

View File

@@ -1,11 +1,24 @@
{
"root": true,
"env": {
"es6": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"extends": ["airbnb-base", "airbnb-typescript/base", "plugin:jest/recommended", "plugin:jest/style", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "tsconfig.json"
"ecmaVersion": 6,
"sourceType": "module",
"project": ["./tsconfig.json", "./packages/**/tsconfig.json"]
},
"plugins": ["@typescript-eslint"]
"plugins": ["@typescript-eslint", "jest"],
"rules": {
"no-underscore-dangle": "off",
"import/no-extraneous-dependencies": "off",
"no-bitwise": "off",
"no-await-in-loop": "off",
"no-restricted-syntax": "off",
"no-console": ["warn", { "allow": ["info", "warn", "error"] }],
"@typescript-eslint/lines-between-class-members": "off",
"no-param-reassign": "off"
}
}

14
.github/ISSUE_TEMPLATE/---package.md vendored Normal file
View File

@@ -0,0 +1,14 @@
---
name: "\U0001F4E6 Package"
about: Propose a new Semaphore package
title: ''
labels: 'feature :rocket:'
assignees: ''
---
**Describe the package you'd like**
A clear and concise description of the type of package you have in mind.
**Additional context**
Add any other context about the package here.

View File

@@ -1,50 +0,0 @@
name: coverall
on:
push:
branches:
- main
env:
TREE_DEPTH: 20
jobs:
coverall:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: YARN_CHECKSUM_BEHAVIOR=ignore yarn
- name: Download Snark artifacts
run: yarn download:snark-artifacts
- name: Compile contracts
run: yarn compile
- name: Test contracts with coverage
run: yarn test:coverage
- uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,13 +1,12 @@
name: style
name: docs
on:
pull_request:
push:
branches:
- main
jobs:
style:
gh-pages:
runs-on: ubuntu-latest
steps:
@@ -32,13 +31,15 @@ jobs:
${{ runner.os }}-yarn-
- name: Install dependencies
run: YARN_CHECKSUM_BEHAVIOR=ignore yarn
run: yarn
- name: Run Prettier
run: yarn prettier
- name: Generate doc website
run: yarn docs
- name: Run Eslint
run: yarn lint
- name: Compile contracts
run: yarn compile
- name: Publish on Github Pages
uses: crazy-max/ghaction-github-pages@v2.5.0
with:
build_dir: docs
jekyll: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

103
.github/workflows/production.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: production
on:
push:
branches:
- main
jobs:
style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- name: Compile contracts
run: yarn compile:contracts
- name: Build libraries
run: yarn build:libraries
- name: Run Prettier
run: yarn prettier
- name: Run Eslint
run: yarn lint
test:
runs-on: ubuntu-latest
strategy:
matrix:
type:
- libraries
- contracts
steps:
- uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- name: Build libraries
run: yarn build:libraries
- name: Test contracts and libraries
run: yarn test:${{ matrix.type }}
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: run-${{ matrix.type }}
path-to-lcov: ./coverage/${{ matrix.type }}/lcov.info
parallel: true
coverage:
runs-on: ubuntu-latest
needs: test
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true

77
.github/workflows/pull-requests.yml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: pull-requests
on:
pull_request:
jobs:
style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- name: Compile contracts
run: yarn compile:contracts
- name: Build libraries
run: yarn build:libraries
- name: Run Prettier
run: yarn prettier
- name: Run Eslint
run: yarn lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- name: Build libraries
run: yarn build:libraries
- name: Test contracts and libraries
run: yarn test

View File

@@ -1,47 +0,0 @@
name: test
on:
pull_request:
push:
branches:
- main
env:
TREE_DEPTH: 20
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: YARN_CHECKSUM_BEHAVIOR=ignore yarn
- name: Download Snark artifacts
run: yarn download:snark-artifacts
- name: Compile contracts
run: yarn compile
- name: Test contracts with coverage
run: yarn test:coverage

17
.gitignore vendored
View File

@@ -16,9 +16,14 @@ pids
*.seed
*.pid.lock
# IDE
.vscode
.idea
# Testing
coverage
coverage.json
*.lcov
# Dependency directories
node_modules/
@@ -28,6 +33,10 @@ node_modules/
# Optional npm cache directory
.npm
.DS_Store
# Output of 'npm pack'
*.tgz
# Optional eslint cache
.eslintcache
@@ -54,8 +63,9 @@ node_modules/
# Production
build
dist
deployed-contracts/undefined.json
deployed-contracts/localhost.json
docs/*
!docs/CNAME
!docs/index.html
# Hardhat
artifacts
@@ -72,3 +82,6 @@ cache
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Other
snark-artifacts

View File

@@ -18,11 +18,12 @@ types
circuits
# contracts
contracts/verifiers
Verifier*.sol
# production
dist
build
docs
# github
.github/ISSUE_TEMPLATE
@@ -35,3 +36,6 @@ build
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# other
snark-artifacts

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -56,7 +56,7 @@ Each commit message consists of a **header**, a **body** and a **footer**. The *
<BLANK LINE>
<footer>
The **header** is mandatory and the **scope** of the header is optional.
The **header** is mandatory and the **scope** of the header must contain the name of the package you are working on.
#### Type

214
README.md
View File

@@ -13,18 +13,18 @@
<a href="https://github.com/semaphore-protocol" target="_blank">
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
<a href="/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/actions?query=workflow%3Atest">
<img alt="GitHub Workflow test" src="https://img.shields.io/github/workflow/status/semaphore-protocol/semaphore/test?label=test&style=flat-square&logo=github">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/actions?query=workflow%3Astyle">
<img alt="GitHub Workflow style" src="https://img.shields.io/github/workflow/status/semaphore-protocol/semaphore/style?label=style&style=flat-square&logo=github">
<a href="https://github.com/semaphore-protocol/semaphore/actions?query=workflow%3Aproduction">
<img alt="GitHub Workflow test" src="https://img.shields.io/github/workflow/status/semaphore-protocol/semaphore/production?label=test&style=flat-square&logo=github">
</a>
<a href="https://coveralls.io/github/semaphore-protocol/semaphore">
<img alt="Coveralls" src="https://img.shields.io/coveralls/github/semaphore-protocol/semaphore?style=flat-square&logo=coveralls">
</a>
<a href="https://deepscan.io/dashboard#view=project&tid=16502&pid=22324&bid=657461">
<img src="https://deepscan.io/api/teams/16502/projects/22324/branches/657461/badge/grade.svg" alt="DeepScan grade">
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint">
</a>
@@ -57,12 +57,149 @@
| 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](/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/contracts) (NPM: `@semaphore-protocol/contracts`) and [JavaScript libraries](https://github.com/semaphore-protocol/semaphore.js) to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
The 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).
---
## 📦 Packages
<table>
<th>Package</th>
<th>Version</th>
<th>Downloads</th>
<tbody>
<tr>
<td>
<a href="/packages/contracts">
@semaphore-protocol/contracts
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/contracts">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/contracts.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/contracts">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/contracts.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/identity">
@semaphore-protocol/identity
</a>
<a href="https://semaphore-protocol.github.io/semaphore/identity">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/identity">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/identity.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/identity">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/identity.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/group">
@semaphore-protocol/group
</a>
<a href="https://semaphore-protocol.github.io/semaphore/group">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/group">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/group.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/group">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/group.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/proof">
@semaphore-protocol/proof
</a>
<a href="https://semaphore-protocol.github.io/semaphore/proof">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/proof">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/proof.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/proof">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/proof.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/subgraph">
@semaphore-protocol/subgraph
</a>
<a href="https://semaphore-protocol.github.io/semaphore/subgraph">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/subgraph.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/subgraph">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/subgraph.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/hardhat">
@semaphore-protocol/hardhat
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/hardhat">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/hardhat.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/hardhat">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/hardhat.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tbody>
</table>
## 🛠 Install
Clone this repository:
@@ -71,7 +208,7 @@ Clone this repository:
git clone https://github.com/semaphore-protocol/semaphore.git
```
and install the dependencies:
And install the dependencies:
```bash
cd semaphore && yarn
@@ -85,7 +222,7 @@ Copy the `.env.example` file as `.env`:
cp .env.example .env
```
and add your environment variables.
And add your environment variables.
### Code quality and formatting
@@ -101,7 +238,7 @@ Run [Prettier](https://prettier.io/) to check formatting rules:
yarn prettier
```
or to automatically format the code:
Or to automatically format the code:
```bash
yarn prettier:write
@@ -125,59 +262,46 @@ Download the Semaphore snark artifacts needed to generate and verify proofs:
yarn download:snark-artifacts
```
### Compile contracts
Compile the smart contracts with [Hardhat](https://hardhat.org/):
```bash
yarn compile
```
### Testing
Run [Jest](https://jestjs.io/) to test the JS libraries:
```bash
yarn test:libraries
```
Run [Mocha](https://mochajs.org/) to test the contracts:
```bash
yarn test:contracts
```
Or test everything with:
```bash
yarn test
```
You can also generate a test coverage report:
### Build libraries & compile contracts
Run [Rollup](https://www.rollupjs.org) to build all the packages:
```bash
yarn test:coverage
yarn build:libraries
```
or a test gas report:
Compile the smart contracts with [Hardhat](https://hardhat.org/):
```bash
yarn test:report-gas
yarn compile:contracts
```
### Deploy contracts
### Documentation (JS libraries)
Deploy a verifier contract with depth = 20:
Run [TypeDoc](https://typedoc.org/) to generate a documentation website for each package:
```bash
yarn deploy:verifier --depth 20
yarn docs
```
Deploy the `Semaphore.sol` contract with one verifier:
```bash
yarn deploy:semaphore --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]'
```
Deploy all verifiers and Semaphore contract:
```bash
yarn deploy:all
```
If you want to deploy contracts in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as option:
```bash
yarn deploy:all --network goerli
yarn deploy:all --network localhost
```
If you want to deploy contracts on Goerli or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file.
The output will be placed on the `docs` folder.

3
babel.config.json Normal file
View File

@@ -0,0 +1,3 @@
{
"presets": [["@babel/preset-env", { "targets": { "node": "current" } }], "@babel/preset-typescript"]
}

View File

@@ -1,74 +0,0 @@
[
{
"name": "Verifier16",
"address": "0x6143ECd9Fd1A00EDe1046d456f8aab53a7D71609"
},
{
"name": "Verifier17",
"address": "0xAc12fFFE354D6446eb50dd33E683B78FED73Fb02"
},
{
"name": "Verifier18",
"address": "0x610aeF0F2da3CD1C8bDefe4BDB434Ee146E0C701"
},
{
"name": "Verifier19",
"address": "0x5477725177035bbC9d70443eb921D29749D6FCb4"
},
{
"name": "Verifier20",
"address": "0x3fB2C0988a37b76e760c44e6516aF720935f3136"
},
{
"name": "Verifier21",
"address": "0xDc8f6B8A42836d4566256f4c6C53131DFD127DF8"
},
{
"name": "Verifier22",
"address": "0x6962b5e706be5278eeCb01c286b50A48484632f2"
},
{
"name": "Verifier23",
"address": "0x41e4796Bd89B4BF04013b559c93fC32E9a2BdF6B"
},
{
"name": "Verifier24",
"address": "0xD528B1D1408ab3583af4694F92b0aFEbE33d5b60"
},
{
"name": "Verifier25",
"address": "0x1683a27EF9c10c5286dB56412E1272cD0Ca733e7"
},
{
"name": "Verifier26",
"address": "0x78194bB665d1E33b97eE45B1A755c15717E94C00"
},
{
"name": "Verifier27",
"address": "0x997Dac00E6701Ef7F3518280E5a9922801126E42"
},
{
"name": "Verifier28",
"address": "0xDd3C7f4cBA2467aE41c0F614A3c3E24bC80268c6"
},
{
"name": "Verifier29",
"address": "0xe53eF12093933D5df5691EAbA3821bD1c1EB60Cd"
},
{
"name": "Verifier30",
"address": "0x7FeA07c536ABBB0E7FB3c833376EE4EaDc21340e"
},
{
"name": "Verifier31",
"address": "0xe4539a592df18936202480FBe77E47DE012F2178"
},
{
"name": "Verifier32",
"address": "0x98c90845A7870e215cBd7265DDC653E6c07032F4"
},
{
"name": "Semaphore",
"address": "0x49281E30F17A30808a6ce538f979d539747e6707"
}
]

View File

@@ -1,74 +0,0 @@
[
{
"name": "Verifier16",
"address": "0xA5253ba39381Aa99c4C2C5A4D5C2deC036d06629"
},
{
"name": "Verifier17",
"address": "0xe0418A5f8fBF051D6cbc41Ff29855Dd2a02201Ab"
},
{
"name": "Verifier18",
"address": "0x7CdB3336d7d7c55Bce0FB1508594C54521656797"
},
{
"name": "Verifier19",
"address": "0xbd870921d8A5398a3314C950d1fc63b8C3AB190B"
},
{
"name": "Verifier20",
"address": "0x2a96c5696F85e3d2aa918496806B5c5a4D93E099"
},
{
"name": "Verifier21",
"address": "0x5Ec7d851a52A2a25CEc528F42a7ACA8EcF4667Cd"
},
{
"name": "Verifier22",
"address": "0x919d3d9c05FA7411e334deA5a763354fC7B6aA5b"
},
{
"name": "Verifier23",
"address": "0x63917b00a6dA7865bEfdd107AfC83CC2e6BDE552"
},
{
"name": "Verifier24",
"address": "0xd05CAd7d940114c1419098EE3cEA0776ab510E7D"
},
{
"name": "Verifier25",
"address": "0x6D9862e6140D94E932d94c8BcE74a0BDD0ea5ACb"
},
{
"name": "Verifier26",
"address": "0x8c29e0b77e32f704F03eeCE01c041192A5EB6c77"
},
{
"name": "Verifier27",
"address": "0x066cC22f8CA2A8D90D7Ff77D8a10A27e629c9c4C"
},
{
"name": "Verifier28",
"address": "0x698F9507f504E2BD238be7da56E8D9fee60C6D15"
},
{
"name": "Verifier29",
"address": "0xbBfC2E201C3c3c6F50063c3Edb4746c6Fcb36346"
},
{
"name": "Verifier30",
"address": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"
},
{
"name": "Verifier31",
"address": "0x133b69Ce47BF20C49368354914DF47519Ca6cCFE"
},
{
"name": "Verifier32",
"address": "0xe2978F79cb4AF62e5C990EE5c7E12fb22ee22e2D"
},
{
"name": "Semaphore",
"address": "0xd688189016277e1a6aE5228ef6894C14585A42D3"
}
]

123
docs/index.html Normal file
View File

@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html style="height: 100%">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="IE=edge" />
<title>Semaphore packages</title>
<meta
name="description"
content="A monorepo of Semaphore packages."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
</head>
<body
style="
margin: 0;
background-color: #EAF0F4;
color: #000;
height: 100%;
font-family: 'Courier New', monospace;
display: flex;
flex-direction: column;
justify-content: space-between;
"
>
<div
style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
padding: 0 20px;
text-align: center;
"
>
<div style="display: flex">
<span style="margin-right: 5px">
<img width="40" src="https://raw.githubusercontent.com/semaphore-protocol/website/main/static/img/semaphore-icon.svg">
</span>
<h1 style="margin: 0; font-size: 40px">Semaphore packages</h1>
</div>
<p style="max-width: 500px">
A monorepo of Semaphore packages.
</p>
<ul style="list-style-type: none; padding: 0; margin: 0; margin-top: 10px"></ul>
</div>
<footer
style="
display: flex;
justify-content: center;
padding: 15px 20px;
background-color: #EAF0F4;
"
>
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
width: 900px;
"
>
<p style="margin: 0; font-size: 16px">
Copyright © 2022 Ethereum Foundation
</p>
<div>
<a
style="margin-right: 15px; text-decoration: none"
target="_blank"
href="https://github.com/semaphore-protocol/semaphore"
>
<i
class="fa fa-github"
style="font-size: 24px; color: #000"
></i>
</a>
</div>
</div>
</footer>
</body>
<script>
const url =
"https://api.github.com/repos/semaphore-protocol/semaphore/contents?ref=gh-pages"
function insertLinks(packages) {
const [element] = window.document.getElementsByTagName("ul")
let html = ""
for (const package of packages) {
html += `<li style="display: flex; align-items: center; margin-bottom: 8px">
<a style="margin-right: 15px" target="_blank" href="https://github.com/semaphore-protocol/semaphore/tree/main/packages/${package}">
<i class="fa fa-github" style="font-size: 24px; color: #000"></i>
</a>
<a style="color: #000; text-decoration: none; font-size: 16px"
onmouseover="this.style.color='#404A4E';"
onmouseout="this.style.color='#000';"
target="_blank" href="https://semaphore-protocol.github.io/semaphore/${package}">
@semaphore-protocol/${package} >
</a></li>`
}
element.innerHTML = html
}
fetch(url)
.then((response) => response.json())
.then((data) => {
const ignore = [".nojekyll", "index.html", "CNAME"]
const packages = data
.map((c) => c.name)
.filter((name) => !ignore.includes(name))
localStorage.setItem("packages", JSON.stringify(packages))
insertLinks(packages)
})
</script>
</html>

29
jest.config.ts Normal file
View File

@@ -0,0 +1,29 @@
import fs from "fs"
import type { Config } from "@jest/types"
const projects: any = fs
.readdirSync("./packages", { withFileTypes: true })
.filter((directory) => directory.isDirectory())
.map(({ name }) => ({
rootDir: `packages/${name}`,
displayName: name,
setupFiles: ["dotenv/config"],
moduleNameMapper: {
"@semaphore-protocol/(.*)": "<rootDir>/../$1/src/index.ts" // Interdependency packages.
}
}))
export default async (): Promise<Config.InitialOptions> => ({
projects,
verbose: true,
coverageDirectory: "./coverage/libraries",
collectCoverageFrom: ["<rootDir>/src/**/*.ts", "!<rootDir>/src/**/index.ts", "!<rootDir>/src/**/*.d.ts"],
coverageThreshold: {
global: {
branches: 90,
functions: 95,
lines: 95,
statements: 95
}
}
})

View File

@@ -1,104 +1,81 @@
{
"name": "semaphore",
"name": "semaphore-protocol",
"description": "A zero-knowledge protocol for anonymous signalling on Ethereum.",
"license": "MIT",
"homepage": "https://github.com/semaphore-protocol/semaphore.git#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/semaphore-protocol/semaphore.git.git"
},
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
"repository": "git@github.com:semaphore-protocol/semaphore.git",
"homepage": "https://github.com/semaphore-protocol/semaphore",
"bugs": "https://github.com/semaphore-protocol/semaphore/issues",
"private": true,
"scripts": {
"start": "hardhat node",
"compile": "hardhat compile",
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
"deploy:all": "hardhat run scripts/deploy-all.ts",
"deploy:verifier": "hardhat deploy:verifier",
"deploy:semaphore": "hardhat deploy:semaphore",
"deploy:semaphore-voting": "hardhat deploy:semaphore-voting",
"deploy:semaphore-whistleblowing": "hardhat deploy:semaphore-whistleblowing",
"test": "hardhat test",
"test:report-gas": "REPORT_GAS=true hardhat test",
"test:coverage": "hardhat coverage",
"typechain": "hardhat typechain",
"lint": "yarn lint:sol && yarn lint:ts",
"lint:ts": "eslint . --ext .js,.ts",
"lint:sol": "solhint 'contracts/**/*.sol'",
"build:libraries": "yarn workspaces foreach run build",
"compile:contracts": "yarn workspace contracts compile",
"download:snark-artifacts": "ts-node scripts/download-snark-artifacts.ts",
"test": "yarn test:libraries && yarn test:contracts",
"test:libraries": "jest --coverage",
"test:contracts": "yarn workspace contracts test:coverage",
"lint": "eslint . --ext .js,.ts && yarn workspace contracts lint",
"prettier": "prettier -c .",
"prettier:write": "prettier -w .",
"docs": "yarn workspaces foreach run docs",
"commit": "cz",
"precommit": "lint-staged"
"precommit": "lint-staged",
"postinstall": "yarn download:snark-artifacts"
},
"keywords": [
"ethereum",
"semaphore",
"solidity",
"circom",
"javascript",
"typescript",
"zero-knowledge",
"zk-snarks",
"zero-knowledge-proofs",
"proof-of-membership",
"monorepo"
],
"workspaces": [
"packages/*"
],
"packageManager": "yarn@3.2.1",
"devDependencies": {
"@commitlint/cli": "^16.1.0",
"@babel/core": "^7.16.7",
"@babel/preset-env": "^7.16.8",
"@babel/preset-typescript": "^7.17.12",
"@commitlint/cli": "^16.0.2",
"@commitlint/config-conventional": "^16.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.6",
"@nomiclabs/hardhat-etherscan": "^3.1.0",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@semaphore-protocol/group": "2.2.0",
"@semaphore-protocol/identity": "2.0.0",
"@semaphore-protocol/proof": "2.3.1",
"@typechain/ethers-v5": "^10.0.0",
"@typechain/hardhat": "^6.0.0",
"@types/chai": "^4.3.0",
"@rollup/plugin-typescript": "^8.3.0",
"@types/download": "^8.0.1",
"@types/mocha": "^9.1.0",
"@types/node": "^17.0.12",
"@types/glob": "^7.2.0",
"@types/jest": "^27.4.0",
"@types/node": "^17.0.9",
"@types/rimraf": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.10.1",
"@typescript-eslint/parser": "^5.10.1",
"chai": "^4.3.5",
"circomlib": "^2.0.2",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"babel-jest": "^27.4.6",
"circomlibjs": "^0.0.8",
"commitizen": "^4.2.4",
"cz-conventional-changelog": "^3.3.0",
"dotenv": "^14.3.2",
"download": "^8.0.0",
"eslint": "^8.7.0",
"dotenv": "^16.0.2",
"eslint": "^8.2.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.3.0",
"ethereum-waffle": "^3.4.4",
"ethers": "^5.6.8",
"hardhat": "^2.9.7",
"hardhat-gas-reporter": "^1.0.8",
"js-logger": "^1.6.1",
"lint-staged": "^12.3.2",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jest": "^25.7.0",
"jest": "^27.4.1",
"jest-config": "^27.4.7",
"lint-staged": "^12.1.7",
"prettier": "^2.5.1",
"prettier-plugin-solidity": "^1.0.0-beta.19",
"rimraf": "^3.0.2",
"snarkjs": "^0.4.13",
"solhint": "^3.3.6",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.21",
"rollup": "^2.64.0",
"ts-node": "^10.4.0",
"typechain": "^8.0.0",
"typescript": "^4.5.5"
"tslib": "^2.3.1",
"typescript": "^4.5.4"
},
"config": {
"solidity": {
"version": "0.8.4"
},
"paths": {
"contracts": "./contracts",
"circuit": "./circuit",
"tests": "./test",
"cache": "./cache",
"snarkjs-templates": "./snarkjs-templates",
"build": {
"snark-artifacts": "./build/snark-artifacts",
"contracts": "./build/contracts",
"typechain": "./build/typechain"
}
},
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"dependencies": {
"@openzeppelin/contracts": "4.4.2",
"@zk-kit/incremental-merkle-tree.sol": "1.3.0"
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "circuits",
"private": true,
"dependencies": {
"circomlib": "^2.0.2"
}
}

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,4 +1,5 @@
module.exports = {
istanbulFolder: "../../coverage/contracts",
skipFiles: [
"verifiers/Verifier16.sol",
"verifiers/Verifier17.sol",

View File

@@ -0,0 +1 @@
contracts/README.md

View File

@@ -7,7 +7,7 @@
<p align="center">
<a href="https://github.com/semaphore-protocol">
<img src="https://img.shields.io/badge/project-semaphore-blue.svg?style=flat-square">
<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">
@@ -59,3 +59,62 @@ or yarn:
```bash
yarn add @semaphore-protocol/contracts
```
## 📜 Usage
### Compile contracts
Compile the smart contracts with [Hardhat](https://hardhat.org/):
```bash
yarn compile
```
### Testing
Run [Mocha](https://mochajs.org/) to test the contracts:
```bash
yarn test
```
You can also generate a test coverage report:
```bash
yarn test:coverage
```
Or a test gas report:
```bash
yarn test:report-gas
```
### Deploy contracts
Deploy a verifier contract with depth = 20:
```bash
yarn deploy:verifier --depth 20
```
Deploy the `Semaphore.sol` contract with one verifier:
```bash
yarn deploy:semaphore --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]'
```
Deploy all verifiers and Semaphore contract:
```bash
yarn deploy:all
```
If you want to deploy contracts in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as option:
```bash
yarn deploy:all --network goerli
yarn deploy:all --network localhost
```
If you want to deploy contracts on Goerli or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file.

View File

@@ -11,16 +11,13 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
/// @dev Gets a tree depth and returns its verifier address.
mapping(uint256 => IVerifier) public verifiers;
/// @dev Gets a group id and returns the group admin address.
mapping(uint256 => address) public groupAdmins;
/// @dev Gets a group id and returns data to check if a Merkle root is expired.
mapping(uint256 => MerkleTreeExpiry) public merkleTreeExpiries;
/// @dev Gets a group id and returns the group parameters.
mapping(uint256 => Group) public groups;
/// @dev Checks if the group admin is the transaction sender.
/// @param groupId: Id of the group.
modifier onlyGroupAdmin(uint256 groupId) {
if (groupAdmins[groupId] != _msgSender()) {
if (groups[groupId].admin != _msgSender()) {
revert Semaphore__CallerIsNotTheGroupAdmin();
}
_;
@@ -56,8 +53,8 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
_createGroup(groupId, merkleTreeDepth, zeroValue);
groupAdmins[groupId] = admin;
merkleTreeExpiries[groupId].rootDuration = 1 hours;
groups[groupId].admin = admin;
groups[groupId].merkleRootDuration = 1 hours;
emit GroupAdminUpdated(groupId, address(0), admin);
}
@@ -72,15 +69,15 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
_createGroup(groupId, merkleTreeDepth, zeroValue);
groupAdmins[groupId] = admin;
merkleTreeExpiries[groupId].rootDuration = merkleTreeRootDuration;
groups[groupId].admin = admin;
groups[groupId].merkleRootDuration = merkleTreeRootDuration;
emit GroupAdminUpdated(groupId, address(0), admin);
}
/// @dev See {ISemaphore-updateGroupAdmin}.
function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) {
groupAdmins[groupId] = newAdmin;
groups[groupId].admin = newAdmin;
emit GroupAdminUpdated(groupId, _msgSender(), newAdmin);
}
@@ -91,7 +88,7 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot] = block.timestamp;
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}
/// @dev See {ISemaphore-addMembers}.
@@ -110,7 +107,7 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot] = block.timestamp;
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}
/// @dev See {ISemaphore-updateMember}.
@@ -150,26 +147,30 @@ contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
}
if (merkleTreeRoot != currentMerkleTreeRoot) {
uint256 rootCreationDate = merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot];
uint256 rootDuration = merkleTreeExpiries[groupId].rootDuration;
uint256 merkleRootCreationDate = groups[groupId].merkleRootCreationDates[merkleTreeRoot];
uint256 merkleRootDuration = groups[groupId].merkleRootDuration;
if (rootCreationDate == 0) {
if (merkleRootCreationDate == 0) {
revert Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
}
if (block.timestamp > rootCreationDate + rootDuration) {
if (block.timestamp > merkleRootCreationDate + merkleRootDuration) {
revert Semaphore__MerkleTreeRootIsExpired();
}
}
if (groups[groupId].nullifierHashes[nullifierHash]) {
revert Semaphore__YouAreUsingTheSameNillifierTwice();
}
uint256 merkleTreeDepth = getMerkleTreeDepth(groupId);
IVerifier verifier = verifiers[merkleTreeDepth];
_verifyProof(signal, merkleTreeRoot, nullifierHash, externalNullifier, proof, verifier);
_saveNullifierHash(nullifierHash);
groups[groupId].nullifierHashes[nullifierHash] = true;
emit ProofVerified(groupId, signal);
emit ProofVerified(groupId, merkleTreeRoot, nullifierHash, externalNullifier, signal);
}
}

View File

@@ -10,10 +10,6 @@ import "../interfaces/IVerifier.sol";
/// nullifier to prevent double-signaling. External nullifier and Merkle trees (i.e. groups) must be
/// managed externally.
contract SemaphoreCore is ISemaphoreCore {
/// @dev Gets a nullifier hash and returns true or false.
/// It is used to prevent double-signaling.
mapping(uint256 => bool) internal nullifierHashes;
/// @dev Asserts that no nullifier already exists and if the zero-knowledge proof is valid.
/// Otherwise it reverts.
/// @param signal: Semaphore signal.
@@ -30,10 +26,6 @@ contract SemaphoreCore is ISemaphoreCore {
uint256[8] calldata proof,
IVerifier verifier
) internal view {
if (nullifierHashes[nullifierHash]) {
revert Semaphore__YouAreUsingTheSameNillifierTwice();
}
uint256 signalHash = _hashSignal(signal);
verifier.verifyProof(
@@ -44,14 +36,6 @@ contract SemaphoreCore is ISemaphoreCore {
);
}
/// @dev Stores the nullifier hash to prevent double-signaling.
/// Attention! Remember to call it when you verify a proof if you
/// need to prevent double-signaling.
/// @param nullifierHash: Semaphore nullifier hash.
function _saveNullifierHash(uint256 nullifierHash) internal {
nullifierHashes[nullifierHash] = true;
}
/// @dev Creates a keccak256 hash of the signal.
/// @param signal: Semaphore signal.
/// @return Hash of the signal.

View File

@@ -12,8 +12,8 @@ import "@openzeppelin/contracts/utils/Context.sol";
abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
using IncrementalBinaryTree for IncrementalTreeData;
/// @dev Gets a group id and returns the group/tree data.
mapping(uint256 => IncrementalTreeData) internal groups;
/// @dev Gets a group id and returns the tree data.
mapping(uint256 => IncrementalTreeData) internal merkleTree;
/// @dev Creates a new group by initializing the associated tree.
/// @param groupId: Id of the group.
@@ -32,7 +32,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
revert Semaphore__GroupAlreadyExists();
}
groups[groupId].init(merkleTreeDepth, zeroValue);
merkleTree[groupId].init(merkleTreeDepth, zeroValue);
emit GroupCreated(groupId, merkleTreeDepth, zeroValue);
}
@@ -45,7 +45,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
revert Semaphore__GroupDoesNotExist();
}
groups[groupId].insert(identityCommitment);
merkleTree[groupId].insert(identityCommitment);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
uint256 index = getNumberOfMerkleTreeLeaves(groupId) - 1;
@@ -71,7 +71,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
revert Semaphore__GroupDoesNotExist();
}
groups[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
merkleTree[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
@@ -95,7 +95,7 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
revert Semaphore__GroupDoesNotExist();
}
groups[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
merkleTree[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
@@ -105,17 +105,17 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
/// @dev See {ISemaphoreGroups-getMerkleTreeRoot}.
function getMerkleTreeRoot(uint256 groupId) public view virtual override returns (uint256) {
return groups[groupId].root;
return merkleTree[groupId].root;
}
/// @dev See {ISemaphoreGroups-getMerkleTreeDepth}.
function getMerkleTreeDepth(uint256 groupId) public view virtual override returns (uint256) {
return groups[groupId].depth;
return merkleTree[groupId].depth;
}
/// @dev See {ISemaphoreGroups-getNumberOfMerkleTreeLeaves}.
function getNumberOfMerkleTreeLeaves(uint256 groupId) public view virtual override returns (uint256) {
return groups[groupId].numberOfLeaves;
return merkleTree[groupId].numberOfLeaves;
}
/// @dev Converts the path indices of a Merkle proof to the identity commitment index in the tree.

View File

@@ -14,6 +14,10 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
/// @dev Gets a poll id and returns the poll data.
mapping(uint256 => Poll) internal polls;
/// @dev Gets a nullifier hash and returns true or false.
/// It is used to prevent double-voting.
mapping(uint256 => bool) internal nullifierHashes;
/// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs.
/// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth).
constructor(Verifier[] memory _verifiers) {
@@ -90,6 +94,10 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
revert Semaphore__PollIsNotOngoing();
}
if (nullifierHashes[nullifierHash]) {
revert Semaphore__YouAreUsingTheSameNillifierTwice();
}
uint256 merkleTreeDepth = getMerkleTreeDepth(pollId);
uint256 merkleTreeRoot = getMerkleTreeRoot(pollId);
@@ -97,8 +105,7 @@ contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
_verifyProof(vote, merkleTreeRoot, nullifierHash, pollId, proof, verifier);
// Prevent double-voting (nullifierHash = hash(pollId + identityNullifier)).
_saveNullifierHash(nullifierHash);
nullifierHashes[nullifierHash] = true;
emit VoteAdded(pollId, vote);
}

View File

@@ -8,17 +8,19 @@ interface ISemaphore {
error Semaphore__MerkleTreeDepthIsNotSupported();
error Semaphore__MerkleTreeRootIsExpired();
error Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
error Semaphore__YouAreUsingTheSameNillifierTwice();
struct Verifier {
address contractAddress;
uint256 merkleTreeDepth;
}
/// It defines all the parameters needed to check whether a
/// zero-knowledge proof generated with a certain Merkle tree is still valid.
struct MerkleTreeExpiry {
uint256 rootDuration;
mapping(uint256 => uint256) rootCreationDates;
/// It defines all the group parameters, in addition to those in the Merkle tree.
struct Group {
address admin;
uint256 merkleRootDuration;
mapping(uint256 => uint256) merkleRootCreationDates;
mapping(uint256 => bool) nullifierHashes;
}
/// @dev Emitted when an admin is assigned to a group.
@@ -29,8 +31,17 @@ interface ISemaphore {
/// @dev Emitted when a Semaphore proof is verified.
/// @param groupId: Id of the group.
/// @param merkleTreeRoot: Root of the Merkle tree.
/// @param externalNullifier: External nullifier.
/// @param nullifierHash: Nullifier hash.
/// @param signal: Semaphore signal.
event ProofVerified(uint256 indexed groupId, bytes32 signal);
event ProofVerified(
uint256 indexed groupId,
uint256 merkleTreeRoot,
uint256 externalNullifier,
uint256 nullifierHash,
bytes32 signal
);
/// @dev Saves the nullifier hash to avoid double signaling and emits an event
/// if the zero-knowledge proof is valid.

View File

@@ -4,8 +4,6 @@ pragma solidity 0.8.4;
/// @title SemaphoreCore interface.
/// @dev Interface of SemaphoreCore contract.
interface ISemaphoreCore {
error Semaphore__YouAreUsingTheSameNillifierTwice();
/// @notice Emitted when a proof is verified correctly and a new nullifier hash is added.
/// @param nullifierHash: Hash of external and identity nullifiers.
event NullifierHashAdded(uint256 nullifierHash);

View File

@@ -8,6 +8,7 @@ interface ISemaphoreVoting {
error Semaphore__MerkleTreeDepthIsNotSupported();
error Semaphore__PollHasAlreadyBeenStarted();
error Semaphore__PollIsNotOngoing();
error Semaphore__YouAreUsingTheSameNillifierTwice();
enum PollState {
Created,

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/contracts",
"version": "2.2.0",
"version": "2.6.1",
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
"license": "MIT",
"files": [
@@ -20,11 +20,8 @@
"circom",
"proof-of-membership"
],
"homepage": "https://github.com/semaphore-protocol/semaphore.git#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/semaphore-protocol/semaphore.git.git"
},
"repository": "https://github.com/semaphore-protocol/semaphore",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts",
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
@@ -32,7 +29,7 @@
"access": "public"
},
"dependencies": {
"@openzeppelin/contracts": "4.4.2",
"@zk-kit/incremental-merkle-tree.sol": "1.3.0"
"@openzeppelin/contracts": "4.7.3",
"@zk-kit/incremental-merkle-tree.sol": "1.3.1"
}
}

View File

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

View File

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

View File

@@ -15,24 +15,26 @@ import "./tasks/deploy-semaphore-voting"
import "./tasks/deploy-semaphore-whistleblowing"
import "./tasks/deploy-verifier"
dotenvConfig({ path: resolve(__dirname, "./.env") })
dotenvConfig({ path: resolve(__dirname, "../../.env") })
function getNetworks(): NetworksUserConfig | undefined {
if (process.env.INFURA_API_KEY && process.env.BACKEND_PRIVATE_KEY) {
const infuraApiKey = process.env.INFURA_API_KEY
const accounts = [`0x${process.env.BACKEND_PRIVATE_KEY}`]
function getNetworks(): NetworksUserConfig {
if (!process.env.INFURA_API_KEY || !process.env.BACKEND_PRIVATE_KEY) {
return {}
}
return {
goerli: {
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
chainId: 5,
accounts
},
arbitrum: {
url: "https://arb1.arbitrum.io/rpc",
chainId: 42161,
accounts
}
const infuraApiKey = process.env.INFURA_API_KEY
const accounts = [`0x${process.env.BACKEND_PRIVATE_KEY}`]
return {
goerli: {
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
chainId: 5,
accounts
},
arbitrum: {
url: "https://arb1.arbitrum.io/rpc",
chainId: 42161,
accounts
}
}
}

View File

@@ -0,0 +1,71 @@
{
"name": "contracts",
"private": true,
"scripts": {
"start": "hardhat node",
"compile": "hardhat compile",
"deploy:verifier": "hardhat deploy:verifier",
"deploy:verifiers": "hardhat run scripts/deploy-verifiers.ts",
"deploy:semaphore": "hardhat deploy:semaphore",
"deploy:semaphore-voting": "hardhat deploy:semaphore-voting",
"deploy:semaphore-whistleblowing": "hardhat deploy:semaphore-whistleblowing",
"verify:contracts": "hardhat run scripts/verify-contracts.ts",
"test": "hardhat test",
"test:report-gas": "REPORT_GAS=true hardhat test",
"test:coverage": "hardhat coverage",
"typechain": "hardhat typechain",
"lint": "solhint 'contracts/**/*.sol'"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.6",
"@nomiclabs/hardhat-etherscan": "^3.1.0",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"@semaphore-protocol/group": "workspace:packages/group",
"@semaphore-protocol/identity": "workspace:packages/identity",
"@semaphore-protocol/proof": "workspace:packages/proof",
"@typechain/ethers-v5": "^10.0.0",
"@typechain/hardhat": "^6.0.0",
"@types/chai": "^4.3.0",
"@types/download": "^8.0.1",
"@types/mocha": "^9.1.0",
"@types/node": "^17.0.12",
"@types/rimraf": "^3.0.2",
"chai": "^4.3.5",
"circomlib": "^2.0.2",
"circomlibjs": "^0.0.8",
"download": "^8.0.0",
"ethereum-waffle": "^3.4.4",
"ethers": "^5.6.8",
"hardhat": "^2.9.7",
"hardhat-gas-reporter": "^1.0.8",
"js-logger": "^1.6.1",
"prettier-plugin-solidity": "^1.0.0-beta.19",
"rimraf": "^3.0.2",
"snarkjs": "^0.4.13",
"solhint": "^3.3.6",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.21",
"ts-node": "^10.4.0",
"typechain": "^8.0.0"
},
"config": {
"solidity": {
"version": "0.8.4"
},
"paths": {
"contracts": "./contracts",
"circuit": "./circuit",
"tests": "./test",
"cache": "./cache",
"snarkjs-templates": "./snarkjs-templates",
"build": {
"contracts": "./build/contracts",
"typechain": "./build/typechain"
}
}
},
"dependencies": {
"@openzeppelin/contracts": "4.7.3",
"@zk-kit/incremental-merkle-tree.sol": "1.3.1"
}
}

View File

@@ -0,0 +1,22 @@
import { hardhatArguments, run } from "hardhat"
import { saveDeployedContracts } from "./utils"
async function main() {
const verifiers: Record<string, string> = {}
// Deploy verifiers.
for (let treeDepth = 16; treeDepth <= 32; treeDepth += 1) {
const { address } = await run("deploy:verifier", { depth: treeDepth })
verifiers[`Verifier${treeDepth}`] = address
}
saveDeployedContracts(hardhatArguments.network, { verifiers })
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@@ -0,0 +1,34 @@
import { readFileSync, writeFileSync } from "fs"
export function saveDeployedContracts(network: string | undefined, deployedContracts: any) {
if (network !== "goerli" && network !== "arbitrum") {
return
}
writeFileSync(`./deployed-contracts/${network}.json`, JSON.stringify(deployedContracts, null, 4))
}
export function getDeployedContracts(network: string | undefined): any {
if (network !== "goerli" && network !== "arbitrum") {
return null
}
return JSON.parse(readFileSync(`./deployed-contracts/${network}.json`, "utf8"))
}
export function verifiersToSolidityArgument(deployedContracts: any): any {
const verifiers = []
if (deployedContracts && deployedContracts.verifiers) {
for (const verifier in deployedContracts.verifiers) {
if (Object.prototype.hasOwnProperty.call(deployedContracts.verifiers, verifier)) {
verifiers.push({
merkleTreeDepth: verifier.substring(8),
contractAddress: deployedContracts.verifiers[verifier]
})
}
}
}
return verifiers
}

View File

@@ -0,0 +1,24 @@
import { hardhatArguments, run } from "hardhat"
import { getDeployedContracts, verifiersToSolidityArgument } from "./utils"
async function main() {
const deployedContracts = getDeployedContracts(hardhatArguments.network)
if (deployedContracts) {
await run("verify:verify", {
address: deployedContracts.Semaphore,
constructorArguments: [verifiersToSolidityArgument(deployedContracts)]
})
await run("verify:verify", {
address: deployedContracts.IncrementalBinaryTree
})
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@@ -8,7 +8,7 @@ task("accounts", "Prints the list of accounts")
if (logs) {
for (const account of accounts) {
console.log(await account.getAddress())
console.info(await account.getAddress())
}
}

View File

@@ -16,7 +16,9 @@ task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract")
await poseidonLib.deployed()
logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`)
if (logs) {
console.info(`Poseidon library has been deployed to: ${poseidonLib.address}`)
}
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
libraries: {
@@ -27,7 +29,9 @@ task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract")
await incrementalBinaryTreeLib.deployed()
logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
if (logs) {
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
}
const ContractFactory = await ethers.getContractFactory("SemaphoreVoting", {
libraries: {
@@ -39,7 +43,9 @@ task("deploy:semaphore-voting", "Deploy a SemaphoreVoting contract")
await contract.deployed()
logs && console.log(`SemaphoreVoting contract has been deployed to: ${contract.address}`)
if (logs) {
console.info(`SemaphoreVoting contract has been deployed to: ${contract.address}`)
}
return contract
})

View File

@@ -16,7 +16,9 @@ task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contra
await poseidonLib.deployed()
logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`)
if (logs) {
console.info(`Poseidon library has been deployed to: ${poseidonLib.address}`)
}
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
libraries: {
@@ -27,7 +29,9 @@ task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contra
await incrementalBinaryTreeLib.deployed()
logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
if (logs) {
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
}
const ContractFactory = await ethers.getContractFactory("SemaphoreWhistleblowing", {
libraries: {
@@ -39,7 +43,9 @@ task("deploy:semaphore-whistleblowing", "Deploy a SemaphoreWhistleblowing contra
await contract.deployed()
logs && console.log(`SemaphoreWhistleblowing contract has been deployed to: ${contract.address}`)
if (logs) {
console.info(`SemaphoreWhistleblowing contract has been deployed to: ${contract.address}`)
}
return contract
})

View File

@@ -0,0 +1,67 @@
import { poseidon_gencontract as poseidonContract } from "circomlibjs"
import { Contract } from "ethers"
import { task, types } from "hardhat/config"
import { getDeployedContracts, saveDeployedContracts, verifiersToSolidityArgument } from "../scripts/utils"
task("deploy:semaphore", "Deploy a Semaphore contract")
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
.addOptionalParam("verifiers", "Tree depths and verifier addresses", [], types.json)
.setAction(async ({ logs, verifiers }, { ethers, hardhatArguments }): Promise<Contract> => {
let deployedContracts: any
if (verifiers.length === 0) {
deployedContracts = getDeployedContracts(hardhatArguments.network)
verifiers = verifiersToSolidityArgument(deployedContracts)
}
const poseidonABI = poseidonContract.generateABI(2)
const poseidonBytecode = poseidonContract.createCode(2)
const [signer] = await ethers.getSigners()
const PoseidonLibFactory = new ethers.ContractFactory(poseidonABI, poseidonBytecode, signer)
const poseidonLib = await PoseidonLibFactory.deploy()
await poseidonLib.deployed()
if (logs) {
console.info(`Poseidon library has been deployed to: ${poseidonLib.address}`)
}
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
libraries: {
PoseidonT3: poseidonLib.address
}
})
const incrementalBinaryTreeLib = await IncrementalBinaryTreeLibFactory.deploy()
await incrementalBinaryTreeLib.deployed()
if (logs) {
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
}
const ContractFactory = await ethers.getContractFactory("Semaphore", {
libraries: {
IncrementalBinaryTree: incrementalBinaryTreeLib.address
}
})
const contract = await ContractFactory.deploy(verifiers)
await contract.deployed()
if (logs) {
console.info(`Semaphore contract has been deployed to: ${contract.address}`)
}
if (deployedContracts) {
deployedContracts.PoseidonT3 = poseidonLib.address
deployedContracts.IncrementalBinaryTree = incrementalBinaryTreeLib.address
deployedContracts.Semaphore = contract.address
saveDeployedContracts(hardhatArguments.network, deployedContracts)
}
return contract
})

View File

@@ -11,7 +11,9 @@ task("deploy:verifier", "Deploy a Verifier contract")
await contract.deployed()
logs && console.log(`Verifier${depth} contract has been deployed to: ${contract.address}`)
if (logs) {
console.info(`Verifier${depth} contract has been deployed to: ${contract.address}`)
}
return contract
})

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable jest/valid-expect */
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { FullProof, generateProof, packToSolidityProof, SolidityProof } from "@semaphore-protocol/proof"
@@ -5,7 +7,6 @@ import { expect } from "chai"
import { constants, Signer, utils } from "ethers"
import { run } from "hardhat"
import { Semaphore } from "../build/typechain"
import { config } from "../package.json"
import { createIdentityCommitments } from "./utils"
describe("Semaphore", () => {
@@ -13,12 +14,12 @@ describe("Semaphore", () => {
let signers: Signer[]
let accounts: string[]
const treeDepth = Number(process.env.TREE_DEPTH)
const treeDepth = Number(process.env.TREE_DEPTH) || 20
const groupId = 1
const members = createIdentityCommitments(3)
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
const wasmFilePath = `../../snark-artifacts/semaphore.wasm`
const zkeyFilePath = `../../snark-artifacts/semaphore.zkey`
before(async () => {
const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth: treeDepth })
@@ -109,12 +110,6 @@ describe("Semaphore", () => {
})
describe("# addMembers", () => {
it("Should not add members if the caller is not the group admin", async () => {
const transaction = contract.connect(signers[1]).addMembers(groupId, [1, 2, 3])
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
})
it("Should add new members to an existing group", async () => {
const groupId = 3
const members = [BigInt(1), BigInt(2), BigInt(3)]
@@ -132,7 +127,9 @@ describe("Semaphore", () => {
describe("# updateMember", () => {
it("Should not update a member if the caller is not the group admin", async () => {
const transaction = contract.connect(signers[1]).updateMember(groupId, members[0], 1, [0, 1], [0, 1])
const member = BigInt(2)
const transaction = contract.connect(signers[1]).updateMember(groupId, member, 1, [0, 1], [0, 1])
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
})
@@ -161,7 +158,9 @@ describe("Semaphore", () => {
describe("# removeMember", () => {
it("Should not remove a member if the caller is not the group admin", async () => {
const transaction = contract.connect(signers[1]).removeMember(groupId, members[0], [0, 1], [0, 1])
const member = BigInt(2)
const transaction = contract.connect(signers[1]).removeMember(groupId, member, [0, 1], [0, 1])
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheGroupAdmin()")
})
@@ -242,7 +241,28 @@ describe("Semaphore", () => {
solidityProof
)
await expect(transaction).to.emit(contract, "ProofVerified").withArgs(groupId, signal)
await expect(transaction)
.to.emit(contract, "ProofVerified")
.withArgs(
groupId,
group.root,
fullProof.publicSignals.nullifierHash,
fullProof.publicSignals.externalNullifier,
signal
)
})
it("Should not verify the same proof for an onchain group twice", async () => {
const transaction = contract.verifyProof(
groupId,
group.root,
signal,
fullProof.publicSignals.nullifierHash,
fullProof.publicSignals.merkleRoot,
solidityProof
)
await expect(transaction).to.be.revertedWith("Semaphore__YouAreUsingTheSameNillifierTwice()")
})
it("Should not verify a proof if the Merkle tree root is expired", async () => {

View File

@@ -1,5 +1,6 @@
import { Identity } from "@semaphore-protocol/identity"
/* eslint-disable jest/valid-expect */
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import {
generateNullifierHash,
generateProof,
@@ -11,20 +12,19 @@ import { expect } from "chai"
import { Signer, utils } from "ethers"
import { ethers, run } from "hardhat"
import { SemaphoreVoting } from "../build/typechain"
import { config } from "../package.json"
describe("SemaphoreVoting", () => {
let contract: SemaphoreVoting
let accounts: Signer[]
let coordinator: string
const treeDepth = Number(process.env.TREE_DEPTH)
const treeDepth = Number(process.env.TREE_DEPTH) || 20
const pollIds = [BigInt(1), BigInt(2), BigInt(3)]
const encryptionKey = BigInt(0)
const decryptionKey = BigInt(0)
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
const wasmFilePath = `../../snark-artifacts/semaphore.wasm`
const zkeyFilePath = `../../snark-artifacts/semaphore.zkey`
before(async () => {
const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth: treeDepth })
@@ -93,35 +93,32 @@ describe("SemaphoreVoting", () => {
})
it("Should not add a voter if the caller is not the coordinator", async () => {
const identity = new Identity()
const identityCommitment = identity.generateCommitment()
const { commitment } = new Identity()
const transaction = contract.addVoter(pollIds[0], identityCommitment)
const transaction = contract.addVoter(pollIds[0], commitment)
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotThePollCoordinator()")
})
it("Should not add a voter if the poll has already been started", async () => {
const identity = new Identity()
const identityCommitment = identity.generateCommitment()
const { commitment } = new Identity()
const transaction = contract.connect(accounts[1]).addVoter(pollIds[0], identityCommitment)
const transaction = contract.connect(accounts[1]).addVoter(pollIds[0], commitment)
await expect(transaction).to.be.revertedWith("Semaphore__PollHasAlreadyBeenStarted()")
})
it("Should add a voter to an existing poll", async () => {
const identity = new Identity("test")
const identityCommitment = identity.generateCommitment()
const { commitment } = new Identity("test")
const transaction = contract.connect(accounts[1]).addVoter(pollIds[1], identityCommitment)
const transaction = contract.connect(accounts[1]).addVoter(pollIds[1], commitment)
await expect(transaction)
.to.emit(contract, "MemberAdded")
.withArgs(
pollIds[1],
0,
identityCommitment,
commitment,
"14787813191318312920980352979830075893203307366494541177071234930769373297362"
)
})
@@ -135,13 +132,12 @@ describe("SemaphoreVoting", () => {
describe("# castVote", () => {
const identity = new Identity("test")
const identityCommitment = identity.generateCommitment()
const vote = "1"
const bytes32Vote = utils.formatBytes32String(vote)
const group = new Group(treeDepth)
group.addMembers([identityCommitment, BigInt(1)])
group.addMembers([identity.commitment, BigInt(1)])
let solidityProof: SolidityProof
let publicSignals: PublicSignals

View File

@@ -1,3 +1,4 @@
/* eslint-disable jest/valid-expect */
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import {
@@ -11,18 +12,17 @@ import { expect } from "chai"
import { Signer, utils } from "ethers"
import { ethers, run } from "hardhat"
import { SemaphoreWhistleblowing } from "../build/typechain"
import { config } from "../package.json"
describe("SemaphoreWhistleblowing", () => {
let contract: SemaphoreWhistleblowing
let accounts: Signer[]
let editor: string
const treeDepth = Number(process.env.TREE_DEPTH)
const treeDepth = Number(process.env.TREE_DEPTH) || 20
const entityIds = [BigInt(1), BigInt(2)]
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
const wasmFilePath = `../../snark-artifacts/semaphore.wasm`
const zkeyFilePath = `../../snark-artifacts/semaphore.zkey`
before(async () => {
const { address: verifierAddress } = await run("deploy:verifier", { logs: false, depth: treeDepth })
@@ -67,26 +67,24 @@ describe("SemaphoreWhistleblowing", () => {
describe("# addWhistleblower", () => {
it("Should not add a whistleblower if the caller is not the editor", async () => {
const identity = new Identity()
const identityCommitment = identity.generateCommitment()
const { commitment } = new Identity()
const transaction = contract.addWhistleblower(entityIds[0], identityCommitment)
const transaction = contract.addWhistleblower(entityIds[0], commitment)
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
})
it("Should add a whistleblower to an existing entity", async () => {
const identity = new Identity("test")
const identityCommitment = identity.generateCommitment()
const { commitment } = new Identity("test")
const transaction = contract.connect(accounts[1]).addWhistleblower(entityIds[0], identityCommitment)
const transaction = contract.connect(accounts[1]).addWhistleblower(entityIds[0], commitment)
await expect(transaction)
.to.emit(contract, "MemberAdded")
.withArgs(
entityIds[0],
0,
identityCommitment,
commitment,
"14787813191318312920980352979830075893203307366494541177071234930769373297362"
)
})
@@ -100,38 +98,36 @@ describe("SemaphoreWhistleblowing", () => {
describe("# removeWhistleblower", () => {
it("Should not remove a whistleblower if the caller is not the editor", async () => {
const identity = new Identity()
const identityCommitment = identity.generateCommitment()
const { commitment } = new Identity()
const group = new Group(treeDepth)
group.addMember(identityCommitment)
group.addMember(commitment)
const { siblings, pathIndices } = group.generateProofOfMembership(0)
const transaction = contract.removeWhistleblower(entityIds[0], identityCommitment, siblings, pathIndices)
const transaction = contract.removeWhistleblower(entityIds[0], commitment, siblings, pathIndices)
await expect(transaction).to.be.revertedWith("Semaphore__CallerIsNotTheEditor()")
})
it("Should remove a whistleblower from an existing entity", async () => {
const identity = new Identity("test")
const identityCommitment = identity.generateCommitment()
const { commitment } = new Identity("test")
const group = new Group(treeDepth)
group.addMember(identityCommitment)
group.addMember(commitment)
const { siblings, pathIndices } = group.generateProofOfMembership(0)
const transaction = contract
.connect(accounts[1])
.removeWhistleblower(entityIds[0], identityCommitment, siblings, pathIndices)
.removeWhistleblower(entityIds[0], commitment, siblings, pathIndices)
await expect(transaction)
.to.emit(contract, "MemberRemoved")
.withArgs(
entityIds[0],
0,
identityCommitment,
commitment,
"15019797232609675441998260052101280400536945603062888308240081994073687793470"
)
})
@@ -139,20 +135,19 @@ describe("SemaphoreWhistleblowing", () => {
describe("# publishLeak", () => {
const identity = new Identity("test")
const identityCommitment = identity.generateCommitment()
const leak = "leak"
const bytes32Leak = utils.formatBytes32String(leak)
const group = new Group(treeDepth)
group.addMembers([identityCommitment, BigInt(1)])
group.addMembers([identity.commitment, BigInt(1)])
let solidityProof: SolidityProof
let publicSignals: PublicSignals
before(async () => {
await contract.createEntity(entityIds[1], editor, treeDepth)
await contract.connect(accounts[1]).addWhistleblower(entityIds[1], identityCommitment)
await contract.connect(accounts[1]).addWhistleblower(entityIds[1], identity.commitment)
await contract.connect(accounts[1]).addWhistleblower(entityIds[1], BigInt(1))
const fullProof = await generateProof(identity, group, entityIds[1], leak, {

View File

@@ -1,13 +1,13 @@
import { Identity } from "@semaphore-protocol/identity"
// eslint-disable-next-line import/prefer-default-export
export function createIdentityCommitments(n: number): bigint[] {
const identityCommitments: bigint[] = []
for (let i = 0; i < n; i++) {
const identity = new Identity(i.toString())
const identityCommitment = identity.generateCommitment()
for (let i = 0; i < n; i += 1) {
const { commitment } = new Identity(i.toString())
identityCommitments.push(identityCommitment)
identityCommitments.push(commitment)
}
return identityCommitments

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2018",
"module": "CommonJS",
"outDir": "dist"
},
"include": ["scripts/**/*", "tasks/**/*", "test/**/*", "build/typechain/**/*", "types/**/*"],
"files": ["hardhat.config.ts"]
}

21
packages/group/LICENSE Normal file
View File

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

127
packages/group/README.md Normal file
View File

@@ -0,0 +1,127 @@
<p align="center">
<h1 align="center">
Semaphore group
</h1>
<p align="center">A library to create and manage Semaphore groups.</p>
</p>
<p align="center">
<a href="https://github.com/semaphore-protocol">
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@semaphore-protocol/group">
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/group?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@semaphore-protocol/group">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/group.svg?style=flat-square" />
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
</a>
<a href="https://prettier.io/">
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
</a>
</p>
<div align="center">
<h4>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
👥 Contributing
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
🤝 Code of conduct
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
🗣️ Chat &amp; Support
</a>
</h4>
</div>
| This library is an abstraction of [`@zk-kit/incremental-merkle-tree`](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/incremental-merkle-tree). The main goal is to make it easier to create offchain groups, which are also used to generate Semaphore proofs. Semaphore groups are actually incremental Merkle trees, and the group members are tree leaves. Since the Merkle tree implementation we are using is a binary tree, the maximum number of members of a group is equal to `2^treeDepth`. |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## 🛠 Install
### npm or yarn
Install the `@semaphore-protocol/group` package with npm:
```bash
npm i @semaphore-protocol/group
```
or yarn:
```bash
yarn add @semaphore-protocol/group
```
## 📜 Usage
\# **new Group**(treeDepth = 20, zeroValue = BigInt(0)): _Group_
```typescript
import { Group } from "@semaphore-protocol/group"
// Group with max 1048576 members (20^²).
const group1 = new Group()
// Group with max 65536 members (16^²).
const group2 = new Group(16)
// Group with max 16777216 members (24^²).
const group3 = new Group(24)
```
\# **addMember**(identityCommitment: _Member_)
```typescript
import { Identity } from "@semaphore-protocol/identity"
const identity = new Identity()
const commitment = identity.generateCommitment()
group.addMember(commitment)
```
\# **addMembers**(identityCommitments: _Member\[]_)
```typescript
let identityCommitments: bigint[]
for (let i = 0; i < 10; i++) {
const identity = new Identity()
const commitment = identity.generateCommitment()
identityCommitments.push(commitment)
}
group.addMember(identityCommitments)
```
\# **removeMember**(index: _number_)
```typescript
group.removeMember(0)
```
\# **indexOf**(member: _Member_): _number_
```typescript
group.indexOf(commitment) // 0
```
\# **generateProofOfMembership**(index: _number_): _MerkleProof_
```typescript
const proof = group.generateProofOfMembership(0)
```

View File

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

View File

@@ -0,0 +1,41 @@
{
"name": "@semaphore-protocol/group",
"version": "2.6.1",
"description": "A library to create and manage Semaphore groups.",
"license": "MIT",
"main": "dist/index.node.js",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.node.js"
},
"types": "dist/types/index.d.ts",
"files": [
"dist/",
"src/",
"LICENSE",
"README.md"
],
"repository": "https://github.com/semaphore-protocol/semaphore",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/group",
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
"scripts": {
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "yarn build",
"docs": "typedoc src/index.ts --out ../../docs/group"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.31.2",
"typedoc": "^0.22.11"
},
"dependencies": {
"@zk-kit/incremental-merkle-tree": "1.0.0",
"circomlibjs": "0.0.8"
}
}

View File

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

View File

@@ -0,0 +1,95 @@
import Group from "./group"
describe("Group", () => {
describe("# Group", () => {
it("Should create a group", () => {
const group = new Group()
expect(group.root.toString()).toContain("150197")
expect(group.depth).toBe(20)
expect(group.zeroValue).toBe(BigInt(0))
expect(group.members).toHaveLength(0)
})
it("Should not create a group with a wrong tree depth", () => {
const fun = () => new Group(33)
expect(fun).toThrow("The tree depth must be between 16 and 32")
})
it("Should create a group with different parameters", () => {
const group = new Group(32, BigInt(1))
expect(group.root.toString()).toContain("640470")
expect(group.depth).toBe(32)
expect(group.zeroValue).toBe(BigInt(1))
expect(group.members).toHaveLength(0)
})
})
describe("# addMember", () => {
it("Should add a member to a group", () => {
const group = new Group()
group.addMember(BigInt(3))
expect(group.members).toHaveLength(1)
})
})
describe("# addMembers", () => {
it("Should add many members to a group", () => {
const group = new Group()
group.addMembers([BigInt(1), BigInt(3)])
expect(group.members).toHaveLength(2)
})
})
describe("# indexOf", () => {
it("Should return the index of a member in a group", () => {
const group = new Group()
group.addMembers([BigInt(1), BigInt(3)])
const index = group.indexOf(BigInt(3))
expect(index).toBe(1)
})
})
describe("# updateMember", () => {
it("Should update a member in a group", () => {
const group = new Group()
group.addMembers([BigInt(1), BigInt(3)])
group.updateMember(0, BigInt(1))
expect(group.members).toHaveLength(2)
expect(group.members[0]).toBe(BigInt(1))
})
})
describe("# removeMember", () => {
it("Should remove a member from a group", () => {
const group = new Group()
group.addMembers([BigInt(1), BigInt(3)])
group.removeMember(0)
expect(group.members).toHaveLength(2)
expect(group.members[0]).toBe(group.zeroValue)
})
})
describe("# generateProofOfMembership", () => {
it("Should generate a proof of membership", () => {
const group = new Group()
group.addMembers([BigInt(1), BigInt(3)])
const proof = group.generateProofOfMembership(0)
expect(proof.leaf).toBe(BigInt(1))
})
})
})

109
packages/group/src/group.ts Normal file
View File

@@ -0,0 +1,109 @@
import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree"
import { poseidon } from "circomlibjs"
import { Member } from "./types"
export default class Group {
private _merkleTree: IncrementalMerkleTree
/**
* Initializes the group with the tree depth and the zero value.
* @param treeDepth Tree depth.
* @param zeroValue Zero values for zeroes.
*/
constructor(treeDepth = 20, zeroValue: Member = BigInt(0)) {
if (treeDepth < 16 || treeDepth > 32) {
throw new Error("The tree depth must be between 16 and 32")
}
this._merkleTree = new IncrementalMerkleTree(poseidon, treeDepth, zeroValue, 2)
}
/**
* Returns the root hash of the tree.
* @returns Root hash.
*/
get root(): Member {
return this._merkleTree.root
}
/**
* Returns the depth of the tree.
* @returns Tree depth.
*/
get depth(): number {
return this._merkleTree.depth
}
/**
* Returns the zero value of the tree.
* @returns Tree zero value.
*/
get zeroValue(): Member {
return this._merkleTree.zeroes[0]
}
/**
* Returns the members (i.e. identity commitments) of the group.
* @returns List of members.
*/
get members(): Member[] {
return this._merkleTree.leaves
}
/**
* Returns the index of a member. If the member does not exist it returns -1.
* @param member Group member.
* @returns Index of the member.
*/
indexOf(member: Member): number {
return this._merkleTree.indexOf(member)
}
/**
* Adds a new member to the group.
* @param identityCommitment New member.
*/
addMember(identityCommitment: Member) {
this._merkleTree.insert(BigInt(identityCommitment))
}
/**
* Adds new members to the group.
* @param identityCommitments New members.
*/
addMembers(identityCommitments: Member[]) {
for (const identityCommitment of identityCommitments) {
this.addMember(identityCommitment)
}
}
/**
* Updates a member in the group.
* @param index Index of the member to be updated.
* @param identityCommitment New member value.
*/
updateMember(index: number, identityCommitment: Member) {
this._merkleTree.update(index, identityCommitment)
}
/**
* Removes a member from the group.
* @param index Index of the member to be removed.
*/
removeMember(index: number) {
this._merkleTree.delete(index)
}
/**
* Creates a proof of membership.
* @param index Index of the proof's member.
* @returns Proof object.
*/
generateProofOfMembership(index: number): MerkleProof {
const merkleProof = this._merkleTree.createProof(index)
merkleProof.siblings = merkleProof.siblings.map((s) => s[0])
return merkleProof
}
}

View File

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

View File

@@ -0,0 +1 @@
export type Member = string | bigint

View File

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

115
packages/hardhat/README.md Normal file
View File

@@ -0,0 +1,115 @@
<p align="center">
<h1 align="center">
Semaphore Hardhat plugin
</h1>
<p align="center">A Semaphore Hardhat plugin to deploy verifiers and Semaphore contracts.</p>
</p>
<p align="center">
<a href="https://github.com/semaphore-protocol">
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@semaphore-protocol/hardhat">
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/hardhat?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@semaphore-protocol/hardhat">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/hardhat.svg?style=flat-square" />
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
</a>
<a href="https://prettier.io/">
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
</a>
</p>
<div align="center">
<h4>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
👥 Contributing
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
🤝 Code of conduct
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
🗣️ Chat &amp; Support
</a>
</h4>
</div>
| This Hardhat plugin provides two simple tasks that can be used to deploy verifiers and Semaphore contracts without any additional configuration. |
| ------------------------------------------------------------------------------------------------------------------------------------------------ |
## 🛠 Install
### npm or yarn
Install the `@semaphore-protocol/hardhat` package with npm:
```bash
npm i @semaphore-protocol/hardhat
```
or yarn:
```bash
yarn add @semaphore-protocol/hardhat
```
## 📜 Usage
Import the plugin in your `hardhat.config.ts` file:
```typescript
import "@semaphore-protocol/hardhat"
import "./tasks/deploy"
const hardhatConfig: HardhatUserConfig = {
solidity: "0.8.4"
}
export default hardhatConfig
```
And use its tasks to create your own `deploy` task and deploy your contract with a Semaphore address.
```typescript
import { task, types } from "hardhat/config"
task("deploy", "Deploy a Greeter contract")
.addOptionalParam("logs", "Print the logs", true, types.boolean)
.setAction(async ({ logs }, { ethers, run }) => {
const { address: verifierAddress } = await run("deploy:verifier", { logs, merkleTreeDepth: 20 })
const { address: semaphoreAddress } = await run("deploy:semaphore", {
logs,
verifiers: [
{
merkleTreeDepth: 20,
contractAddress: verifierAddress
}
]
})
const Greeter = await ethers.getContractFactory("Greeter")
const greeter = await Greeter.deploy(semaphoreAddress)
await greeter.deployed()
if (logs) {
console.log(`Greeter contract has been deployed to: ${greeter.address}`)
}
return greeter
})
```

View File

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

View File

@@ -0,0 +1,46 @@
{
"name": "@semaphore-protocol/hardhat",
"version": "0.1.0",
"description": "A Semaphore Hardhat plugin to deploy verifiers and Semaphore contract.",
"license": "MIT",
"main": "dist/index.node.js",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.node.js"
},
"types": "dist/types/index.d.ts",
"files": [
"dist/",
"src/",
"LICENSE",
"README.md"
],
"repository": "https://github.com/semaphore-protocol/semaphore",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/hardhat",
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
"scripts": {
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "yarn build"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"hardhat": "^2.0.0",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.31.2"
},
"peerDependencies": {
"hardhat": "^2.0.0"
},
"dependencies": {
"@nomiclabs/hardhat-ethers": "^2.1.1",
"@semaphore-protocol/contracts": "^2.5.0",
"circomlibjs": "^0.0.8",
"ethers": "^5.7.1",
"hardhat-dependency-compiler": "^1.1.3"
}
}

View File

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

View File

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

View File

@@ -3,8 +3,8 @@ import { Contract } from "ethers"
import { task, types } from "hardhat/config"
task("deploy:semaphore", "Deploy a Semaphore contract")
.addParam("verifiers", "Tree depths and verifier addresses", [], types.json)
.addOptionalParam<boolean>("logs", "Print the logs", true, types.boolean)
.addParam("verifiers", "Tree depths and verifier addresses", undefined, types.json)
.setAction(async ({ logs, verifiers }, { ethers }): Promise<Contract> => {
const poseidonABI = poseidonContract.generateABI(2)
const poseidonBytecode = poseidonContract.createCode(2)
@@ -16,7 +16,9 @@ task("deploy:semaphore", "Deploy a Semaphore contract")
await poseidonLib.deployed()
logs && console.log(`Poseidon library has been deployed to: ${poseidonLib.address}`)
if (logs) {
console.info(`Poseidon library has been deployed to: ${poseidonLib.address}`)
}
const IncrementalBinaryTreeLibFactory = await ethers.getContractFactory("IncrementalBinaryTree", {
libraries: {
@@ -27,19 +29,23 @@ task("deploy:semaphore", "Deploy a Semaphore contract")
await incrementalBinaryTreeLib.deployed()
logs && console.log(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
if (logs) {
console.info(`IncrementalBinaryTree library has been deployed to: ${incrementalBinaryTreeLib.address}`)
}
const ContractFactory = await ethers.getContractFactory("Semaphore", {
const SemaphoreContractFactory = await ethers.getContractFactory("Semaphore", {
libraries: {
IncrementalBinaryTree: incrementalBinaryTreeLib.address
}
})
const contract = await ContractFactory.deploy(verifiers)
const semaphoreContract = await SemaphoreContractFactory.deploy(verifiers)
await contract.deployed()
await semaphoreContract.deployed()
logs && console.log(`Semaphore contract has been deployed to: ${contract.address}`)
if (logs) {
console.info(`Semaphore contract has been deployed to: ${semaphoreContract.address}`)
}
return contract
return semaphoreContract
})

View File

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

View File

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

21
packages/identity/LICENSE Normal file
View File

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

View File

@@ -0,0 +1,86 @@
<p align="center">
<h1 align="center">
Semaphore identity
</h1>
<p align="center">A library to create Semaphore identities.</p>
</p>
<p align="center">
<a href="https://github.com/semaphore-protocol">
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@semaphore-protocol/identity">
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/identity?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@semaphore-protocol/identity">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/identity.svg?style=flat-square" />
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
</a>
<a href="https://prettier.io/">
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
</a>
</p>
<div align="center">
<h4>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
👥 Contributing
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
🤝 Code of conduct
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://discord.gg/6mSdGHnstH">
🗣️ Chat &amp; Support
</a>
</h4>
</div>
| This library provides a class that can be used to create identities compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Each identity contains two secret values: _trapdoor_ and _nullifier_, and one public value: _commitment_. The Poseidon hash of the secret values is the identity secret, and its hash is the identity commitment. |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## 🛠 Install
### npm or yarn
Install the `@semaphore-protocol/identity` package with npm:
```bash
npm i @semaphore-protocol/identity
```
or yarn:
```bash
yarn add @semaphore-protocol/identity
```
## 📜 Usage
\# **new Identity**(identityOrMessage?: _string_): _Identity_
```typescript
import { Identity } from "@semaphore-protocol/identity"
// The identity can be generated randomly.
const identity1 = new Identity()
// Deterministically from a secret message.
const identity2 = new Identity("secret-message")
// Or it can be retrieved from an existing identity.
const identity3 = new Identity(identity1.toString())
// Trapdoor, nullifier and commitment are the attributes (e.g. JS getters).
const { trapdoor, nullifier, commitment } = identity1
```

View File

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

View File

@@ -0,0 +1,44 @@
{
"name": "@semaphore-protocol/identity",
"version": "2.6.1",
"description": "A library to create Semaphore identities.",
"license": "MIT",
"main": "dist/index.node.js",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.node.js"
},
"types": "dist/types/index.d.ts",
"files": [
"dist/",
"src/",
"LICENSE",
"README.md"
],
"repository": "https://github.com/semaphore-protocol/semaphore",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/identity",
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
"scripts": {
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "yarn build",
"docs": "typedoc src/index.ts --out ../../docs/identity"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.31.2",
"typedoc": "^0.22.11"
},
"dependencies": {
"@ethersproject/bignumber": "^5.5.0",
"@ethersproject/random": "^5.5.1",
"@ethersproject/sha2": "^5.6.1",
"@ethersproject/strings": "^5.6.1",
"circomlibjs": "0.0.8"
}
}

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