Compare commits

...

88 Commits

Author SHA1 Message Date
cedoor
35e994d8ab chore: v4.0.0-alpha
Former-commit-id: dfc0629b04
2024-01-23 17:02:21 +00:00
Cedoor
09f4671112 Merge pull request #571 from semaphore-protocol/chore/update-rem-pkgs
Update subgraph and data/CLI libraries with new contracts

Former-commit-id: d0e8bb1dd4
2024-01-23 16:54:03 +00:00
Cedoor
9fec3138f7 Merge pull request #578 from semaphore-protocol/chore/oz-deployer
OpenZeppelin Defender deployer

Former-commit-id: c8f6d3ad46
2024-01-23 16:51:11 +00:00
Cedoor
d83e06e833 Merge pull request #569 from semaphore-protocol/chore/new-testnets
Remove Goerli from supported networks

Former-commit-id: 4e33b01d1f
2024-01-23 16:22:09 +00:00
cedoor
0529774445 test(data): update ethers tests
Former-commit-id: a637d6c705
2024-01-23 15:46:19 +00:00
cedoor
aa9329007c style(cli): update cli templates' contract artifacts
Former-commit-id: 983472570d
2024-01-23 15:29:53 +00:00
cedoor
aacff93bd8 chore(cli): update cli monorepo templates
Former-commit-id: 23151872a5
2024-01-23 15:28:16 +00:00
cedoor
58aac07f7a refactor(identity): convert priv key buffer to bigint
Former-commit-id: 27df0d55c1
2024-01-23 15:28:16 +00:00
cedoor
ac51bba9ea chore(contracts): update proof pkg var names
Former-commit-id: 364977da0d
2024-01-23 15:28:16 +00:00
cedoor
0acf57b9b1 refactor(proof): include merkle depth in the proof
Former-commit-id: 4221412367
2024-01-23 15:28:16 +00:00
cedoor
c91b09d7c5 chore(hardhat): update hardhat package dependencies
Former-commit-id: 2cd7484c62
2024-01-23 15:28:16 +00:00
cedoor
66b6ddd165 chore(cli): update contracts-hardhat template
Former-commit-id: 7441b930e8
2024-01-23 15:28:16 +00:00
cedoor
eecef0e77a fix: adjust cli and data packages
Former-commit-id: 7b447f9f63
2024-01-23 15:28:16 +00:00
cedoor
b3101f0a0a refactor(subgraph): update merkle tree var name
Former-commit-id: f50892525b
2024-01-23 15:28:16 +00:00
cedoor
4c365862ae chore: update cli and data packages
Former-commit-id: 0c0b5b0984
2024-01-23 15:28:16 +00:00
cedoor
be46c9c05d refactor(subgraph): set correct type for tree depth
Former-commit-id: ee8db0edcf
2024-01-23 15:28:16 +00:00
cedoor
a290ecbfdc revert(contracts): re-add script to verify contracts
Former-commit-id: 968fb10e1c
2024-01-23 15:28:16 +00:00
cedoor
d5e8c94af9 chore(subgraph): update docker compose file
Former-commit-id: 0080f3163f
2024-01-23 15:28:16 +00:00
cedoor
f3806dd7cb refactor(contracts): improve task to deploy
Former-commit-id: d593f484dd
2024-01-23 15:28:16 +00:00
cedoor
22ad55e1eb style(subgraph): format code with prettier
Former-commit-id: 7a1ba9ef4a
2024-01-23 15:28:16 +00:00
cedoor
667f890a93 chore(subgraph): update subgraph with new contract events
re #543


Former-commit-id: e231afb30f
2024-01-23 15:28:16 +00:00
cedoor
546ddba38e style(contracts): format code with prettier
Former-commit-id: 2ea27978a4
2024-01-23 14:37:56 +00:00
cedoor
b714501de0 chore(contracts): deploy contracts on sepolia
Former-commit-id: ceb7f902cd
2024-01-23 14:37:56 +00:00
cedoor
22b54a9e80 perf(contracts): merge verifiers
Former-commit-id: 0ec9abb962
2024-01-23 14:37:56 +00:00
cedoor
4e9c930c27 chore: update env file
Former-commit-id: 0964eda363
2024-01-23 14:37:56 +00:00
cedoor
93d8aadf2f chore(contracts): add open-zeppelin deployer
Former-commit-id: f8753f42fb
2024-01-23 14:37:56 +00:00
cedoor
25d3bee901 style: format code with prettier
Former-commit-id: 741fe54083
2024-01-23 14:36:51 +00:00
cedoor
b817f7e186 test(data): temporary comment tests for sepolia testnets
Former-commit-id: ac92b477c0
2024-01-23 14:32:41 +00:00
cedoor
fd4d51ca40 chore: improve scripts to clean repo
Former-commit-id: a24e5b1a8e
2024-01-17 13:53:15 +00:00
cedoor
94566774f9 style(subgraph): format code with prettier
Former-commit-id: 0640e06242
2024-01-17 13:15:29 +00:00
cedoor
52f139d2f4 chore: remove goerli from supported networks
re #352


Former-commit-id: 15ffd2695b
2024-01-17 13:07:05 +00:00
Cedoor
5d532c1bff Merge pull request #566 from semaphore-protocol/feat/add-many-contracts-new-event
Add batch insertion in contracts

Former-commit-id: 6f8cecbcd7
2024-01-17 11:50:13 +00:00
vplasencia
62cb8d02f6 Merge branch 'feat/semaphore-v4' into feat/add-many-contracts-new-event
Former-commit-id: 7f13bb18e5
2024-01-17 10:13:41 +01:00
Vivian Plasencia
acda38f666 Merge pull request #568 from semaphore-protocol/chore/zk-artifacts
New ZK artifacts & verifiers to support tree depths 1-12

Former-commit-id: 70a85e9e6b
2024-01-17 10:08:37 +01:00
cedoor
f31d95e834 style(contracts): format code with prettier
Former-commit-id: 8175e0d99f
2024-01-16 19:04:02 +00:00
cedoor
95b38ba7dd refactor(proof): support multi tree depths (1-12)
Former-commit-id: a60f1f21d0
2024-01-16 18:47:31 +00:00
cedoor
ba898915d7 chore(contracts): remove base contracts
Former-commit-id: 921f699c8e
2024-01-16 18:46:34 +00:00
cedoor
6d1af90fab refactor(contracts): update tests, tasks and scripts
Former-commit-id: 8a8c21c2f1
2024-01-16 18:46:17 +00:00
cedoor
d42e5c875e refactor(contracts): add 1 verifier for each tree depth
Former-commit-id: 937f2ee930
2024-01-16 18:45:33 +00:00
vplasencia
24828b41a4 docs(contracts): add code docs for the members added event
Former-commit-id: 8d1270e80b
2024-01-16 18:27:16 +01:00
vplasencia
7033b00c2b test(contracts): remove skip tests
Former-commit-id: 65a219373a
2024-01-16 18:02:01 +01:00
vplasencia
5dfd8c1cba refactor(contracts): optimize gas
Former-commit-id: b0f40ef5c9
2024-01-16 17:54:37 +01:00
cedoor
2b5ec257cf refactor(circuits): rename privateKey with secret
Former-commit-id: 076e5de03c
2024-01-16 16:06:12 +00:00
vplasencia
409cc624fa test(contracts): update add members test
Former-commit-id: dd1e3a1249
2024-01-16 10:29:35 +01:00
vplasencia
0ff15f5c92 chore(contracts): update imt version
Former-commit-id: 415fe35031
2024-01-16 09:56:03 +01:00
vplasencia
6ec5eb8319 chore(contracts): update zk-kit imt version
Former-commit-id: e6eceafb0d
2024-01-15 18:26:42 +01:00
vplasencia
8cafe09de6 feat(contracts): add many members using new event
Former-commit-id: d27da573ae
2024-01-15 09:57:15 +01:00
cedoor
bd4d30fd19 ci: remove unnecessary cache actions from workflows
Former-commit-id: 3c7ce0e47c
2024-01-12 10:54:42 +00:00
cedoor
cf66732974 feat(contracts): add new function to verify proofs without nullifiers
Former-commit-id: 0367d238df
2024-01-12 10:54:42 +00:00
cedoor
1c300672d0 chore(circuits): udpate zk-kit dependency
Former-commit-id: dea0d4c016
2024-01-12 10:54:42 +00:00
cedoor
19a6768a1d chore(contracts): update solidity version
Former-commit-id: c95cc8beaf
2024-01-12 10:54:42 +00:00
cedoor
175b5485dc test(proof): add longer timeout for generiting proofs
Former-commit-id: d3d82c0c55
2024-01-12 10:54:42 +00:00
cedoor
2666c69728 chore(contracts): downgrade prettier & solhint packages
Former-commit-id: 004803916c
2024-01-12 10:54:42 +00:00
cedoor
b9b12184b5 refactor(contracts): update script to verify contracts
Former-commit-id: c9c99c7855
2024-01-12 10:54:42 +00:00
cedoor
38bd1747a0 chore(contracts): removed unused snarkjs templates
Former-commit-id: 85acdb7801
2024-01-12 10:54:42 +00:00
cedoor
59b8fd30b6 chore(contracts): update solidity and hardhat versions
Former-commit-id: 94453ad50e
2024-01-12 10:54:42 +00:00
Jeeiii
5151249a10 docs: add missing external references for EdDSA and Poseidon
Former-commit-id: 617d0c6dd1
2024-01-12 10:54:42 +00:00
Jeeiii
e30dcca7d6 fix: typo for ISemaphore contract interface custom error
Former-commit-id: aa29a2fdee
2024-01-12 10:54:42 +00:00
Jeeiii
ba99809deb chore: update copyright year
Former-commit-id: 1847f0bc40
2024-01-12 10:54:42 +00:00
Jeeiii
3fb4b5effc refactor: update npm types for node up to latest NodeJS LTS version (20)
Former-commit-id: 81cc94840b
2024-01-12 10:54:42 +00:00
Jeeiii
d8f9078471 build: move github actions to V4 along with new NodeJS LTS version (20)
Former-commit-id: 6ee2499571
2024-01-12 10:54:42 +00:00
Jeeiii
14227d2088 fix: use merkleTreeSize instead of merkleTreeDepth to support groups with one member
Former-commit-id: 5e7cd2693c
2024-01-12 10:54:42 +00:00
Cedoor
af7187a687 Update packages/proof/README.md
Co-authored-by: Giacomo <giacomo.corrias7@gmail.com>
Former-commit-id: ac26789818
2024-01-12 10:54:42 +00:00
cedoor
7572e921a4 refactor(circuits): update merkle proof variable names
Former-commit-id: 5cf4226272
2024-01-12 10:54:42 +00:00
cedoor
908b541fab fix(proof): set correct var name
Former-commit-id: 1c591e1213
2024-01-12 10:54:42 +00:00
cedoor
4c513c26f5 refactor(circuits): update variable name
Former-commit-id: 89c9b6d063
2024-01-12 10:54:42 +00:00
cedoor
fcecad1146 chore(proof): create env bundles
Former-commit-id: 195c87dc2e
2024-01-12 10:54:42 +00:00
cedoor
ee819255d6 docs: update readme files
Former-commit-id: 5f3b266c18
2024-01-12 10:54:42 +00:00
cedoor
d1e9cdcb16 refactor(contracts): fix eslint warnings
Former-commit-id: bd025350c6
2024-01-12 10:54:42 +00:00
cedoor
c45137e373 chore: move test files outside src
Former-commit-id: 7e69ffd743
2024-01-12 10:54:42 +00:00
cedoor
7c61118a40 refactor(hardhat): update hardhat pkg tasks
Former-commit-id: b162ae60e4
2024-01-12 10:54:42 +00:00
cedoor
17cad2b93c refactor(heyauthn): update tests and doc
Former-commit-id: c8da0dc575
2024-01-12 10:54:42 +00:00
cedoor
7a10acb153 refactor(contracts): move access logic to group contract
Former-commit-id: 2b05a94e15
2024-01-12 10:54:42 +00:00
cedoor
ef1f22a043 refactor: hash message and scope
Former-commit-id: b7d58bdac8
2024-01-12 10:54:42 +00:00
cedoor
37d081e1d0 fix(contracts): reverse proof b points
Former-commit-id: 4d4a31cb28
2024-01-12 10:54:42 +00:00
cedoor
6a427480cd chore(proof): update zk-kit groth16 pkg version
Former-commit-id: 7708477001
2024-01-12 10:54:42 +00:00
cedoor
7d781dc09b refactor: update verifiers
Former-commit-id: 26195e3944
2024-01-12 10:54:42 +00:00
cedoor
a93b590d42 feat(contracts): update contracts with new imt
Former-commit-id: cc32feeb27
2024-01-12 10:54:42 +00:00
cedoor
32fd451eb9 refactor(contracts): remove extension contracts
Former-commit-id: 6b347ff5fb
2024-01-12 10:54:42 +00:00
cedoor
a5bb7a6c73 chore: update env variables
Former-commit-id: 5983ed6e08
2024-01-12 10:54:42 +00:00
cedoor
ddf0045699 feat(proof): update proof package with new circuit
Former-commit-id: bc5b294323
2024-01-12 10:54:42 +00:00
cedoor
c0cb8fb0a7 refactor(identity): update commitment variable name
Former-commit-id: f80bb04d19
2024-01-12 10:54:42 +00:00
cedoor
f3e896eaaf feat(group): update group with lean-imt
Former-commit-id: 1c8e2185b9
2024-01-12 10:54:42 +00:00
cedoor
361510d206 refactor(identity): add identity commitment attribute
Former-commit-id: 202e4bead4
2024-01-12 10:54:42 +00:00
cedoor
00953a9233 feat(identity): update identity with eddsa
Former-commit-id: fee7f10a3d
2024-01-12 10:54:42 +00:00
cedoor
ae7be9a47a chore(circuits): update circom configuration
re #357


Former-commit-id: 267a250c43
2024-01-12 10:54:42 +00:00
cedoor
b3a0e5ac8d ci: add circuit tests to workflows
re #357


Former-commit-id: 5c8881cc86
2024-01-12 10:54:42 +00:00
cedoor
3aa5241334 feat(circuits): update semaphore circuits
re #357


Former-commit-id: 4a01dfb179
2024-01-12 10:54:42 +00:00
198 changed files with 3807 additions and 4972 deletions

View File

@@ -1,8 +1,10 @@
DEFAULT_NETWORK=hardhat
TREE_DEPTH=20
ALL_SNARK_ARTIFACTS=true
TREE_DEPTH=10
REPORT_GAS=false
BACKEND_PRIVATE_KEY=
INFURA_API_KEY=
COINMARKETCAP_API_KEY=
ETHERSCAN_API_KEY=
DEFENDER_KEY=
DEFENDER_SECRET=
CREATE2_SALT=1234

View File

@@ -10,25 +10,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
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-
node-version: 20
cache: yarn
- name: Install dependencies
run: yarn

View File

@@ -6,33 +6,20 @@ on:
- main
env:
TREE_DEPTH: 20
ALL_SNARK_ARTIFACTS: false
TREE_DEPTH: 10
jobs:
style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
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-
node-version: 20
cache: yarn
- name: Install dependencies
run: yarn
@@ -56,25 +43,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
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-
node-version: 20
cache: yarn
- name: Install dependencies
run: yarn
@@ -90,29 +65,25 @@ jobs:
strategy:
matrix:
type:
- circuits
- libraries
- contracts
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20
cache: yarn
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
# https://github.com/iden3/circuits/blob/8fffb6609ecad0b7bcda19bb908bdb544bdb3cf7/.github/workflows/main.yml#L18-L22
- name: Setup Circom deps
run: sudo apt-get update && sudo apt-get install -y wget nlohmann-json3-dev libgmp-dev nasm g++ build-essential
- 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: Setup Circom
run: wget https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64 && sudo mv ./circom-linux-amd64 /usr/bin/circom && sudo chmod +x /usr/bin/circom
- name: Install dependencies
run: yarn
@@ -120,10 +91,11 @@ jobs:
- name: Build libraries
run: yarn build:libraries
- name: Test contracts and libraries
- name: Test libraries, contracts and circuits
run: yarn test:${{ matrix.type }}
- name: Coveralls
if: matrix.type != 'circuits'
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -4,33 +4,20 @@ on:
pull_request:
env:
TREE_DEPTH: 20
ALL_SNARK_ARTIFACTS: false
TREE_DEPTH: 10
jobs:
style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
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-
node-version: 20
cache: yarn
- name: Install dependencies
run: yarn
@@ -54,25 +41,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20
cache: yarn
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
# https://github.com/iden3/circuits/blob/8fffb6609ecad0b7bcda19bb908bdb544bdb3cf7/.github/workflows/main.yml#L18-L22
- name: Setup Circom deps
run: sudo apt-get update && sudo apt-get install -y wget nlohmann-json3-dev libgmp-dev nasm g++ build-essential
- 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: Setup Circom
run: wget https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64 && sudo mv ./circom-linux-amd64 /usr/bin/circom && sudo chmod +x /usr/bin/circom
- name: Install dependencies
run: yarn
@@ -83,5 +65,5 @@ jobs:
- name: Build subgraph
run: yarn build:subgraph
- name: Test contracts, libraries and subgraph
- name: Test contracts, libraries, circuits and subgraph
run: yarn test

View File

@@ -13,27 +13,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
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-
node-version: 20
cache: yarn
- name: Install dependencies
run: yarn

View File

@@ -16,12 +16,6 @@ packages/contracts/deployed-contracts/undefined.json
packages/contracts/deployed-contracts/hardhat.json
packages/contracts/deployed-contracts/localhost.json
# circuits
circuits
# contracts
Verifier*.sol
# production
dist
build

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Ethereum Foundation
Copyright (c) 2024 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

View File

@@ -392,6 +392,6 @@
"message": "X (Twitter)"
},
"footer.copyright": {
"message": "Copyright © 2023 Ethereum Foundation"
"message": "Copyright © 2024 Ethereum Foundation"
}
}

View File

@@ -392,6 +392,6 @@
"message": "X (Twitter)"
},
"footer.copyright": {
"message": "Copyright © 2023 Ethereum Foundation"
"message": "Copyright © 2024 Ethereum Foundation"
}
}

View File

@@ -6,3 +6,5 @@ subgraph.yaml
# Tests
/tests/.bin
/data

View File

@@ -42,13 +42,13 @@
## Networks
| Semaphore version | Sepolia | Goerli | Mumbai | Optimism Goerli | Arbitrum Goerli | Arbitrum One |
| ----------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| v2.0 | N/A | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum) |
| v2.5 | N/A | [semaphore-protocol/goerli](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli) | N/A | N/A | N/A | N/A |
| v2.6 | N/A | [semaphore-protocol/goerli-5259d3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli-5259d3) | N/A | N/A | N/A | [semaphore-protocol/arbitrum-86337c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-86337c) |
| v3.0 - v3.1 | N/A | [semaphore-protocol/goerli-89490c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/goerli-89490c) | N/A | N/A | N/A | [semaphore-protocol/arbitrum-72dca3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-72dca3) |
| >= v3.2 | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v3.6.1) | [semaphore-goerli](https://api.studio.thegraph.com/query/14377/semaphore-goerli/v3.6.1) | [semaphore-mumbai](https://api.studio.thegraph.com/query/14377/semaphore-mumbai/v3.6.1) | [semaphore-optimism-goerli](https://api.studio.thegraph.com/query/14377/semaphore-optimism-goerli/v3.6.1) | [semaphore-arbitrum-goerli](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum-goerli/v3.6.1) | [semaphore-arbitrum](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.6.1) |
| Semaphore version | Sepolia | Mumbai | Optimism Sepolia | Arbitrum Sepolia | Arbitrum One |
| ----------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------- |
| v2.0 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum) |
| v2.5 | N/A | N/A | N/A | N/A | N/A |
| v2.6 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum-86337c](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-86337c) |
| v3.0 - v3.1 | N/A | N/A | N/A | N/A | [semaphore-protocol/arbitrum-72dca3](https://thegraph.com/hosted-service/subgraph/semaphore-protocol/arbitrum-72dca3) |
| >= v3.2 | [semaphore-sepolia](https://api.studio.thegraph.com/query/14377/semaphore-sepolia/v3.6.1) | [semaphore-mumbai](https://api.studio.thegraph.com/query/14377/semaphore-mumbai/v3.6.1) | N/A | N/A | [semaphore-arbitrum](https://api.studio.thegraph.com/query/14377/semaphore-arbitrum/v3.6.1) |
## 🛠 Install
@@ -111,15 +111,15 @@ yarn deploy <subgraph-name>
Start services required for TheGraph node by running:
```bash
docker compose -f docker-compose-graph.yml up
docker compose up
```
Start a local Hardhat node and deploy the [Semaphore contract](https://github.com/semaphore-protocol/semaphore/tree/main/packages/contracts):
```bash
# CWD = /semaphore/packages/contracts
yarn start
yarn deploy:semaphore --network localhost
yarn start --hostname 0.0.0.0
yarn deploy --network localhost
```
Create the `subgraph.yaml` file for your local network and create/deploy your subgraph:

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,6 @@
version: "3.9"
# https://github.com/graphprotocol/graph-node/blob/master/docker/docker-compose.yml
version: "3"
services:
graph-node:
@@ -12,49 +14,35 @@ services:
depends_on:
- ipfs
- postgres
extra_hosts:
- host.docker.internal:host-gateway
environment:
postgres_host: postgres
postgres_user: graph-node
postgres_pass: let-me-in
postgres_db: graph-node
postgres_port: 5432
ipfs: "ipfs:5001"
ethereum: "localhost:http://host.docker.internal:8545" # Should use the `localhost` as network name in subgraph.yml
# ethereum: 'goerli:https://goerli.infura.io/v3/YOUR-API-KEY'
ethereum: "localhost:http://host.docker.internal:8545"
GRAPH_LOG: info
GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 1
networks:
- the-graph
ipfs:
image: ipfs/go-ipfs:v0.4.23
image: ipfs/kubo:v0.14.0
ports:
- "5001:5001"
volumes:
- graph-ipfs:/data/ipfs
networks:
- the-graph
- ./data/ipfs:/data/ipfs
postgres:
image: postgres
image: postgres:14
ports:
- "5433:5432"
command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"]
- "5432:5432"
command: ["postgres", "-cshared_preload_libraries=pg_stat_statements", "-cmax_connections=200"]
environment:
POSTGRES_USER: graph-node
POSTGRES_PASSWORD: let-me-in
POSTGRES_DB: graph-node
# FIXME: remove this env. var. which we shouldn't need. Introduced by
# <https://github.com/graphprotocol/graph-node/pull/3511>, maybe as a
# workaround for https://github.com/docker/for-mac/issues/6270?
PGDATA: "/var/lib/postgresql/data"
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C"
networks:
- the-graph
volumes:
- graph-postgres:/var/lib/postgresql/data
networks:
the-graph:
internal: false
driver: bridge
volumes:
graph-postgres:
graph-ipfs:
- ./data/postgres:/var/lib/postgresql/data

View File

@@ -1,43 +1,37 @@
{
"sepolia": {
"Semaphore": {
"address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
"startBlock": 3231111
}
},
"goerli": {
"Semaphore": {
"address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
"startBlock": 8777695
"address": "0x021dC8BF0eADd9C128490A976756C1b052edF99d",
"startBlock": 5108003
}
},
"mumbai": {
"Semaphore": {
"address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
"startBlock": 33995010
"address": "",
"startBlock": 0
}
},
"optimism-goerli": {
"optimism-sepolia": {
"Semaphore": {
"address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
"startBlock": 7632846
"address": "",
"startBlock": 0
}
},
"arbitrum-goerli": {
"arbitrum-sepolia": {
"Semaphore": {
"address": "0x3889927F0B5Eb1a02C6E2C20b39a1Bd4EAd76131",
"startBlock": 15174410
"address": "",
"startBlock": 0
}
},
"arbitrum-one": {
"Semaphore": {
"address": "0xc60E0Ee1a2770d5F619858C641f14FC4a6401520",
"startBlock": 77278430
"address": "",
"startBlock": 0
}
},
"localhost": {
"Semaphore": {
"address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
"address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
"startBlock": 0
}
}

View File

@@ -5,18 +5,19 @@
"license": "MIT",
"private": true,
"scripts": {
"codegen": "node scripts/generateSubgraph.js ${0} && graph codegen",
"codegen": "node scripts/generate-subgraph.js ${0} && graph codegen",
"build": "graph build",
"auth": "graph auth --studio",
"deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ ${0}",
"start-ipfs": "node scripts/start-ipfs.js",
"create-local": "graph create --node http://localhost:8020/ semaphore",
"remove-local": "graph remove --node http://localhost:8020/ semaphore",
"deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 semaphore",
"test": "graph test Semaphore -v 0.5.0"
},
"dependencies": {
"@graphprotocol/graph-cli": "0.56.0",
"@graphprotocol/graph-ts": "^0.31.0"
"@graphprotocol/graph-cli": "0.67.0",
"@graphprotocol/graph-ts": "0.32.0"
},
"devDependencies": {
"@types/mustache": "^4.2.2",

View File

@@ -1,35 +1,36 @@
type MerkleTree @entity {
id: ID!
depth: BigInt!
depth: Int!
root: BigInt
zeroValue: BigInt!
numberOfLeaves: Int!
size: Int!
group: Group!
}
type Group @entity {
id: ID!
merkleTree: MerkleTree!
timestamp: BigInt!
merkleTree: MerkleTree!
admin: Bytes
members: [Member!] @derivedFrom(field: "group")
verifiedProofs: [VerifiedProof!] @derivedFrom(field: "group")
validatedProofs: [ValidatedProof!] @derivedFrom(field: "group")
}
type Member @entity {
id: ID!
identityCommitment: BigInt!
timestamp: BigInt!
identityCommitment: BigInt!
index: Int!
group: Group!
}
type VerifiedProof @entity {
type ValidatedProof @entity {
id: ID!
signal: BigInt!
merkleTreeRoot: BigInt!
nullifierHash: BigInt!
externalNullifier: BigInt!
timestamp: BigInt!
message: BigInt!
scope: BigInt!
merkleTreeRoot: BigInt!
merkleTreeDepth: Int!
nullifier: BigInt!
proof: [BigInt!]!
group: Group!
}

View File

@@ -1,13 +1,14 @@
import { ByteArray, log } from "@graphprotocol/graph-ts"
import { BigInt, ByteArray, log } from "@graphprotocol/graph-ts"
import {
GroupAdminUpdated,
GroupCreated,
MemberAdded,
MemberRemoved,
MemberUpdated,
ProofVerified
MembersAdded,
ProofValidated
} from "../generated/Semaphore/Semaphore"
import { Member, Group, VerifiedProof, MerkleTree } from "../generated/schema"
import { Group, Member, MerkleTree, ValidatedProof } from "../generated/schema"
import { concat, hash } from "./utils"
/**
@@ -22,9 +23,8 @@ export function createGroup(event: GroupCreated): void {
log.info("Creating group '{}'", [group.id])
merkleTree.depth = event.params.merkleTreeDepth
merkleTree.zeroValue = event.params.zeroValue
merkleTree.numberOfLeaves = 0
merkleTree.depth = 0
merkleTree.size = 0
merkleTree.group = group.id
group.timestamp = event.block.timestamp
@@ -76,12 +76,12 @@ export function addMember(event: MemberAdded): void {
member.group = merkleTree.group
member.identityCommitment = event.params.identityCommitment
member.timestamp = event.block.timestamp
member.index = merkleTree.numberOfLeaves
member.index = merkleTree.size
member.save()
merkleTree.root = event.params.merkleTreeRoot
merkleTree.numberOfLeaves += 1
merkleTree.size += 1
merkleTree.save()
@@ -138,7 +138,7 @@ export function removeMember(event: MemberRemoved): void {
if (member) {
log.info("Removing member '{}' from the onchain group '{}'", [member.id, merkleTree.group])
member.identityCommitment = merkleTree.zeroValue
member.identityCommitment = BigInt.fromI32(0)
member.save()
@@ -152,39 +152,83 @@ export function removeMember(event: MemberRemoved): void {
}
/**
* Adds a verified proof in a group.
* @param event Ethereum event emitted when a proof has been verified.
* Adds N members to a group.
* @param event Ethereum event emitted when many members are added to a group.
*/
export function addVerifiedProof(event: ProofVerified): void {
log.debug(`ProofVerified event block {}`, [event.block.number.toString()])
export function addMembers(event: MembersAdded): void {
log.debug(`MembersAdded event block {}`, [event.block.number.toString()])
const merkleTree = MerkleTree.load(event.params.groupId.toString())
// eslint-disable-next-line prefer-destructuring
const identityCommitments = event.params.identityCommitments
// eslint-disable-next-line prefer-destructuring
const startIndex = event.params.startIndex
if (merkleTree) {
for (let i = 0; i < identityCommitments.length; i += 1) {
const identityCommitment = identityCommitments[i]
const memberId = hash(
concat(ByteArray.fromI32(startIndex.toI32() + i), ByteArray.fromBigInt(event.params.groupId))
)
const member = new Member(memberId)
log.info("Adding member '{}' in the onchain group '{}'", [member.id, merkleTree.group])
member.group = merkleTree.group
member.identityCommitment = identityCommitment
member.timestamp = event.block.timestamp
member.index = startIndex.toI32() + i
member.save()
log.info("Member '{}' of the onchain group '{}' has been added", [member.id, merkleTree.id])
}
merkleTree.root = event.params.merkleTreeRoot
merkleTree.size += identityCommitments.length
merkleTree.save()
}
}
/**
* Adds a validated proof in a group.
* @param event Ethereum event emitted when a proof has been validated.
*/
export function addValidatedProof(event: ProofValidated): void {
log.debug(`ProofValidated event block {}`, [event.block.number.toString()])
const group = Group.load(event.params.groupId.toString())
if (group) {
const verifiedProofId = hash(
concat(ByteArray.fromBigInt(event.params.nullifierHash), ByteArray.fromBigInt(event.params.groupId))
const validatedProofId = hash(
concat(ByteArray.fromBigInt(event.params.nullifier), ByteArray.fromBigInt(event.params.groupId))
)
const verifiedProof = new VerifiedProof(verifiedProofId)
const validatedProof = new ValidatedProof(validatedProofId)
log.info("Adding verified proof with signal '{}' in the onchain group '{}'", [
event.params.signal.toHexString(),
log.info("Adding validated proof with message '{}' in the onchain group '{}'", [
event.params.message.toHexString(),
group.id
])
verifiedProof.group = group.id
verifiedProof.signal = event.params.signal
verifiedProof.merkleTreeRoot = event.params.merkleTreeRoot
verifiedProof.externalNullifier = event.params.externalNullifier
verifiedProof.nullifierHash = event.params.nullifierHash
verifiedProof.timestamp = event.block.timestamp
validatedProof.group = group.id
validatedProof.message = event.params.message
validatedProof.merkleTreeRoot = event.params.merkleTreeRoot
validatedProof.merkleTreeDepth = event.params.merkleTreeDepth.toI32()
validatedProof.scope = event.params.scope
validatedProof.nullifier = event.params.nullifier
validatedProof.proof = event.params.proof
validatedProof.timestamp = event.block.timestamp
verifiedProof.save()
validatedProof.save()
group.save()
log.info("Verified proof with signal '{}' in the onchain group '{}' has been added", [
event.params.signal.toHexString(),
log.info("Validated proof with message '{}' in the onchain group '{}' has been added", [
event.params.message.toHexString(),
group.id
])
}

View File

@@ -20,7 +20,7 @@ dataSources:
- name: Semaphore
file: ./abis/Semaphore.json
eventHandlers:
- event: GroupCreated(indexed uint256,uint256,uint256)
- event: GroupCreated(indexed uint256)
handler: createGroup
- event: GroupAdminUpdated(indexed uint256,indexed address,indexed address)
handler: updateGroupAdmin
@@ -30,6 +30,8 @@ dataSources:
handler: updateMember
- event: MemberRemoved(indexed uint256,uint256,uint256,uint256)
handler: removeMember
- event: ProofVerified(indexed uint256,indexed uint256,uint256,indexed uint256,uint256)
handler: addVerifiedProof
- event: MembersAdded(indexed uint256,uint256,uint256[],uint256)
handler: addMembers
- event: ProofValidated(indexed uint256,uint256,indexed uint256,uint256,uint256,indexed uint256,uint256[8])
handler: addValidatedProof
file: ./src/semaphore.ts

View File

@@ -1,5 +1,5 @@
import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"
import { newMockEvent } from "matchstick-as"
import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts"
import {
GroupAdminUpdated,
GroupCreated,
@@ -7,21 +7,16 @@ import {
MemberAdded,
MemberRemoved,
MemberUpdated,
ProofVerified
MembersAdded,
ProofValidated
} from "../generated/Semaphore/Semaphore"
export function createGroupCreatedEvent(groupId: BigInt, merkleTreeDepth: BigInt, zeroValue: BigInt): GroupCreated {
export function createGroupCreatedEvent(groupId: BigInt): GroupCreated {
const groupCreatedEvent = changetype<GroupCreated>(newMockEvent())
groupCreatedEvent.parameters = []
groupCreatedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
groupCreatedEvent.parameters.push(
new ethereum.EventParam("merkleTreeDepth", ethereum.Value.fromUnsignedBigInt(merkleTreeDepth))
)
groupCreatedEvent.parameters.push(
new ethereum.EventParam("zeroValue", ethereum.Value.fromUnsignedBigInt(zeroValue))
)
return groupCreatedEvent
}
@@ -132,29 +127,56 @@ export function createMemberUpdatedEvent(
return memberUpdatedEvent
}
export function createProofVerifiedEvent(
export function createMembersAddedEvent(
groupId: BigInt,
merkleTreeRoot: BigInt,
externalNullifier: BigInt,
nullifierHash: BigInt,
signal: BigInt
): ProofVerified {
const proofVerifiedEvent = changetype<ProofVerified>(newMockEvent())
startIndex: BigInt,
identityCommitments: BigInt[],
merkleTreeRoot: BigInt
): MembersAdded {
const membersAddedEvent = changetype<MembersAdded>(newMockEvent())
proofVerifiedEvent.parameters = []
membersAddedEvent.parameters = []
proofVerifiedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
proofVerifiedEvent.parameters.push(
membersAddedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
membersAddedEvent.parameters.push(
new ethereum.EventParam("startIndex", ethereum.Value.fromUnsignedBigInt(startIndex))
)
membersAddedEvent.parameters.push(
new ethereum.EventParam("identityCommitments", ethereum.Value.fromUnsignedBigIntArray(identityCommitments))
)
membersAddedEvent.parameters.push(
new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot))
)
proofVerifiedEvent.parameters.push(
new ethereum.EventParam("nullifierHash", ethereum.Value.fromUnsignedBigInt(nullifierHash))
)
proofVerifiedEvent.parameters.push(
new ethereum.EventParam("externalNullifier", ethereum.Value.fromUnsignedBigInt(externalNullifier))
)
proofVerifiedEvent.parameters.push(new ethereum.EventParam("signal", ethereum.Value.fromUnsignedBigInt(signal)))
return proofVerifiedEvent
return membersAddedEvent
}
export function createProofVerifiedEvent(
groupId: BigInt,
merkleTreeDepth: BigInt,
merkleTreeRoot: BigInt,
nullifier: BigInt,
message: BigInt,
scope: BigInt,
proof: BigInt[]
): ProofValidated {
const proofValidatedEvent = changetype<ProofValidated>(newMockEvent())
proofValidatedEvent.parameters = []
proofValidatedEvent.parameters.push(new ethereum.EventParam("groupId", ethereum.Value.fromUnsignedBigInt(groupId)))
proofValidatedEvent.parameters.push(
new ethereum.EventParam("merkleTreeDepth", ethereum.Value.fromUnsignedBigInt(merkleTreeDepth))
)
proofValidatedEvent.parameters.push(
new ethereum.EventParam("merkleTreeRoot", ethereum.Value.fromUnsignedBigInt(merkleTreeRoot))
)
proofValidatedEvent.parameters.push(
new ethereum.EventParam("nullifier", ethereum.Value.fromUnsignedBigInt(nullifier))
)
proofValidatedEvent.parameters.push(new ethereum.EventParam("message", ethereum.Value.fromUnsignedBigInt(message)))
proofValidatedEvent.parameters.push(new ethereum.EventParam("scope", ethereum.Value.fromUnsignedBigInt(scope)))
proofValidatedEvent.parameters.push(new ethereum.EventParam("proof", ethereum.Value.fromUnsignedBigIntArray(proof)))
return proofValidatedEvent
}

View File

@@ -1,8 +1,10 @@
/* eslint-disable jest/expect-expect */
import { Address, BigInt, ByteArray } from "@graphprotocol/graph-ts"
import { afterAll, assert, clearStore, describe, test } from "matchstick-as/assembly/index"
import {
addMember,
addVerifiedProof,
addMembers,
addValidatedProof,
createGroup,
removeMember,
updateGroupAdmin,
@@ -15,6 +17,7 @@ import {
createMemberAddedEvent,
createMemberRemovedEvent,
createMemberUpdatedEvent,
createMembersAddedEvent,
createProofVerifiedEvent
} from "./semaphore-utils"
@@ -27,12 +30,10 @@ describe("Semaphore subgraph", () => {
describe("# createGroup", () => {
test("Should have created a group", () => {
const groupId = BigInt.fromI32(234)
const merkleTreeDepth = BigInt.fromI32(20)
const zeroValue = BigInt.fromI32(0)
const oldAdmin = Address.fromString("0x0000000000000000000000000000000000000000")
const newAdmin = Address.fromString("0x0000000000000000000000000000000000000001")
const event1 = createGroupCreatedEvent(groupId, merkleTreeDepth, zeroValue)
const event1 = createGroupCreatedEvent(groupId)
const event2 = createGroupAdminUpdatedEvent(groupId, oldAdmin, newAdmin)
createGroup(event1)
@@ -44,9 +45,8 @@ describe("Semaphore subgraph", () => {
assert.fieldEquals("Group", groupId.toString(), "admin", "0x0000000000000000000000000000000000000001")
assert.fieldEquals("Group", groupId.toString(), "merkleTree", groupId.toString())
assert.fieldEquals("MerkleTree", groupId.toString(), "depth", "20")
assert.fieldEquals("MerkleTree", groupId.toString(), "zeroValue", "0")
assert.fieldEquals("MerkleTree", groupId.toString(), "numberOfLeaves", "0")
assert.fieldEquals("MerkleTree", groupId.toString(), "depth", "0")
assert.fieldEquals("MerkleTree", groupId.toString(), "size", "0")
assert.fieldEquals("MerkleTree", groupId.toString(), "group", groupId.toString())
})
})
@@ -84,7 +84,7 @@ describe("Semaphore subgraph", () => {
assert.fieldEquals("Member", id, "group", groupId.toString())
assert.fieldEquals("MerkleTree", groupId.toString(), "root", "999")
assert.fieldEquals("MerkleTree", groupId.toString(), "numberOfLeaves", "1")
assert.fieldEquals("MerkleTree", groupId.toString(), "size", "1")
})
})
@@ -131,26 +131,61 @@ describe("Semaphore subgraph", () => {
})
})
describe("# addMembers", () => {
test("Should have added many group members at once", () => {
const groupId = BigInt.fromI32(234)
const startIndex = BigInt.fromI32(1)
const identityCommitments = [BigInt.fromI32(123), BigInt.fromI32(124)]
const merkleTreeRoot = BigInt.fromI32(999)
const id = hash(concat(ByteArray.fromBigInt(startIndex), ByteArray.fromBigInt(groupId)))
const event = createMembersAddedEvent(groupId, startIndex, identityCommitments, merkleTreeRoot)
addMembers(event)
assert.entityCount("Member", 3)
assert.fieldEquals("Member", id, "index", "1")
assert.fieldEquals("Member", id, "identityCommitment", "123")
assert.fieldEquals("Member", id, "group", groupId.toString())
assert.fieldEquals("MerkleTree", groupId.toString(), "root", "999")
assert.fieldEquals("MerkleTree", groupId.toString(), "size", "3")
})
})
describe("# addVerifiedProof", () => {
test("Should have added a proof", () => {
const groupId = BigInt.fromI32(234)
const merkleTreeDepth = BigInt.fromI32(32)
const merkleTreeRoot = BigInt.fromI32(1001)
const externalNullifier = BigInt.fromI32(1)
const nullifierHash = BigInt.fromI32(666)
const signal = BigInt.fromI32(2)
const id = hash(concat(ByteArray.fromBigInt(nullifierHash), ByteArray.fromBigInt(groupId)))
const nullifier = BigInt.fromI32(666)
const message = BigInt.fromI32(2)
const scope = BigInt.fromI32(1)
const proof = [BigInt.fromI32(1), BigInt.fromI32(2)]
const id = hash(concat(ByteArray.fromBigInt(nullifier), ByteArray.fromBigInt(groupId)))
const event = createProofVerifiedEvent(groupId, merkleTreeRoot, externalNullifier, nullifierHash, signal)
const event = createProofVerifiedEvent(
groupId,
merkleTreeDepth,
merkleTreeRoot,
nullifier,
message,
scope,
proof
)
addVerifiedProof(event)
addValidatedProof(event)
assert.entityCount("VerifiedProof", 1)
assert.entityCount("ValidatedProof", 1)
assert.fieldEquals("VerifiedProof", id, "merkleTreeRoot", "1001")
assert.fieldEquals("VerifiedProof", id, "externalNullifier", "1")
assert.fieldEquals("VerifiedProof", id, "nullifierHash", "666")
assert.fieldEquals("VerifiedProof", id, "signal", "2")
assert.fieldEquals("VerifiedProof", id, "group", groupId.toString())
assert.fieldEquals("ValidatedProof", id, "group", groupId.toString())
assert.fieldEquals("ValidatedProof", id, "merkleTreeRoot", "1001")
assert.fieldEquals("ValidatedProof", id, "merkleTreeDepth", "32")
assert.fieldEquals("ValidatedProof", id, "scope", "1")
assert.fieldEquals("ValidatedProof", id, "nullifier", "666")
assert.fieldEquals("ValidatedProof", id, "message", "2")
assert.fieldEquals("ValidatedProof", id, "proof", `[${proof.join(", ")}]`)
})
})
})

View File

@@ -88,7 +88,7 @@ export default function Footer() {
</Link>
<Text fontSize={{ base: "12px", md: "14px" }} color="text.500" pt="2">
Copyright © 2023 Ethereum Foundation
Copyright © 2024 Ethereum Foundation
</Text>
</VStack>
</VStack>

View File

@@ -103,7 +103,7 @@ export default function Navbar() {
</Link>
<Text fontSize={{ base: "12px", md: "14px" }} color="text.500" pt="2">
Copyright © 2023 Ethereum Foundation
Copyright © 2024 Ethereum Foundation
</Text>
</VStack>
</VStack>

View File

@@ -1,9 +1,12 @@
import fs from "fs"
import type { Config } from "@jest/types"
const exclude = ["circuits", "contracts"]
const projects: any = fs
.readdirSync("./packages", { withFileTypes: true })
.filter((directory) => directory.isDirectory())
.filter((directory) => !exclude.includes(directory.name))
.map(({ name }) => ({
rootDir: `packages/${name}`,
displayName: name,

View File

@@ -8,24 +8,25 @@
"private": true,
"scripts": {
"build:libraries": "yarn workspaces foreach -t --no-private run build",
"build:subgraph": "yarn workspace semaphore-subgraph codegen goerli && yarn workspace semaphore-subgraph build",
"build:subgraph": "yarn workspace semaphore-subgraph codegen sepolia && yarn workspace semaphore-subgraph build",
"compile:contracts": "yarn workspace semaphore-contracts compile",
"download:snark-artifacts": "rimraf snark-artifacts && ts-node scripts/download-snark-artifacts.ts",
"remove:template-files": "ts-node scripts/remove-template-files.ts",
"test": "yarn test:libraries && yarn test:contracts && yarn test:subgraph",
"test": "yarn test:libraries && yarn test:contracts && yarn test:circuits && yarn test:subgraph",
"test:libraries": "jest --coverage",
"test:subgraph": "yarn workspace semaphore-subgraph test",
"test:contracts": "yarn workspace semaphore-contracts test:coverage",
"test:circuits": "yarn workspace @semaphore-protocol/circuits test",
"lint": "eslint . --ext .js,.ts,.tsx && yarn workspace semaphore-contracts lint",
"prettier": "prettier -c .",
"prettier:write": "prettier -w .",
"docs": "typedoc --cname js.semaphore.pse.dev --githubPages true",
"version:bump": "yarn workspaces foreach --no-private version -d ${0} && yarn version apply --all && git commit -am \"chore: v${0}\" && git tag v${0}",
"version:publish": "yarn build:libraries && yarn remove:template-files && yarn workspaces foreach --no-private npm publish --tolerate-republish --access public",
"version:publish": "yarn build:libraries && yarn clean:cli-templates && yarn workspaces foreach --no-private npm publish --tolerate-republish --access public",
"version:release": "changelogithub",
"clean": "ts-node scripts/clean-apps.ts && ts-node scripts/clean-packages.ts && yarn clean:cli-templates && rimraf node_modules",
"clean:cli-templates": "ts-node scripts/clean-cli-templates.ts",
"commit": "cz",
"precommit": "lint-staged",
"postinstall": "yarn download:snark-artifacts && husky install"
"postinstall": "husky install"
},
"keywords": [
"ethereum",
@@ -57,9 +58,8 @@
"@types/download": "^8.0.1",
"@types/glob": "^7.2.0",
"@types/jest": "^27.4.0",
"@types/node": "^17.0.9",
"@types/node": "^20",
"@types/rimraf": "^3.0.2",
"@types/snarkjs": "^0.7.5",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"babel-jest": "^27.4.6",
@@ -83,6 +83,7 @@
"prettier": "^2.5.1",
"rimraf": "^3.0.2",
"rollup": "^2.64.0",
"snarkjs": "^0.7.2",
"ts-node": "^10.4.0",
"tslib": "^2.3.1",
"typedoc": "^0.25.1",

3
packages/circuits/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
ptau
main
test

View File

@@ -0,0 +1,7 @@
{
"extension": ["ts"],
"require": "ts-node/register",
"spec": "./tests/*.test.ts",
"timeout": 100000,
"exit": true
}

21
packages/circuits/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 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

@@ -9,8 +9,14 @@
<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 href="https://github.com/semaphore-protocol/semaphore/tree/main/packages/circuits/LICENSE">
<img alt="NPM license" src="https://img.shields.io/npm/l/%40semaphore-protocol%2Fcircuits?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@semaphore-protocol/circuits">
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/circuits?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@semaphore-protocol/circuits">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/circuits.svg?style=flat-square" />
</a>
</p>

View File

@@ -0,0 +1,17 @@
{
"protocol": "groth16",
"prime": "bn128",
"version": "2.1.5",
"circuits": "./circuits.json",
"dirPtau": "./ptau",
"dirCircuits": "./",
"dirInputs": "./inputs",
"dirBuild": "./build",
"optimization": 2,
"inspect": true,
"include": ["../../node_modules/circomlib/circuits", "../../node_modules/@zk-kit/circuits/circom"],
"groth16numContributions": 1,
"groth16askForEntropy": false,
"logLevel": "INFO",
"verbose": true
}

View File

@@ -0,0 +1,8 @@
{
"semaphore": {
"file": "semaphore",
"template": "Semaphore",
"pubs": ["message", "scope"],
"params": [10]
}
}

View File

@@ -1,7 +1,39 @@
{
"name": "circuits",
"private": true,
"name": "@semaphore-protocol/circuits",
"version": "4.0.0-alpha",
"description": "Semaphore Circom circuits to generate zero-knowledge proofs.",
"license": "MIT",
"files": [
"**/*.circom",
"!main",
"!test",
"LICENSE",
"README.md"
],
"repository": "https://github.com/semaphore-protocol/semaphore",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/data",
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
"scripts": {
"compile": "circomkit compile semaphore",
"setup": "circomkit setup semaphore",
"test": "mocha"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"circomlib": "^2.0.2"
}
"@zk-kit/circuits": "0.2.4",
"circomlib": "2.0.5"
},
"devDependencies": {
"@types/mocha": "^10.0.6",
"@zk-kit/eddsa-poseidon": "0.3.1",
"@zk-kit/imt": "^2.0.0-beta",
"circomkit": "^0.0.19",
"mocha": "^10.2.0",
"poseidon-lite": "^0.2.0"
},
"stableVersion": "3.15.1"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,90 +1,25 @@
pragma circom 2.0.0;
pragma circom 2.1.5;
include "../node_modules/circomlib/circuits/poseidon.circom";
include "./tree.circom";
include "babyjub.circom";
include "poseidon.circom";
include "binary-merkle-root.circom";
template CalculateSecret() {
signal input identityNullifier;
signal input identityTrapdoor;
signal output out;
component poseidon = Poseidon(2);
poseidon.inputs[0] <== identityNullifier;
poseidon.inputs[1] <== identityTrapdoor;
out <== poseidon.out;
}
template CalculateIdentityCommitment() {
template Semaphore(MAX_DEPTH) {
signal input secret;
signal input merkleProofLength, merkleProofIndices[MAX_DEPTH], merkleProofSiblings[MAX_DEPTH];
signal input message;
signal input scope;
signal output out;
signal output merkleRoot, nullifier;
component poseidon = Poseidon(1);
var Ax, Ay;
(Ax, Ay) = BabyPbk()(secret);
poseidon.inputs[0] <== secret;
var identityCommitment = Poseidon(2)([Ax, Ay]);
out <== poseidon.out;
merkleRoot <== BinaryMerkleRoot(MAX_DEPTH)(identityCommitment, merkleProofLength, merkleProofIndices, merkleProofSiblings);
nullifier <== Poseidon(2)([scope, secret]);
// Dummy constraint to prevent compiler from optimizing it.
signal dummySquare <== message * message;
}
template CalculateNullifierHash() {
signal input externalNullifier;
signal input identityNullifier;
signal output out;
component poseidon = Poseidon(2);
poseidon.inputs[0] <== externalNullifier;
poseidon.inputs[1] <== identityNullifier;
out <== poseidon.out;
}
// The current Semaphore smart contracts require nLevels <= 32 and nLevels >= 16.
template Semaphore(nLevels) {
signal input identityNullifier;
signal input identityTrapdoor;
signal input treePathIndices[nLevels];
signal input treeSiblings[nLevels];
signal input signalHash;
signal input externalNullifier;
signal output root;
signal output nullifierHash;
component calculateSecret = CalculateSecret();
calculateSecret.identityNullifier <== identityNullifier;
calculateSecret.identityTrapdoor <== identityTrapdoor;
signal secret;
secret <== calculateSecret.out;
component calculateIdentityCommitment = CalculateIdentityCommitment();
calculateIdentityCommitment.secret <== secret;
component calculateNullifierHash = CalculateNullifierHash();
calculateNullifierHash.externalNullifier <== externalNullifier;
calculateNullifierHash.identityNullifier <== identityNullifier;
component inclusionProof = MerkleTreeInclusionProof(nLevels);
inclusionProof.leaf <== calculateIdentityCommitment.out;
for (var i = 0; i < nLevels; i++) {
inclusionProof.siblings[i] <== treeSiblings[i];
inclusionProof.pathIndices[i] <== treePathIndices[i];
}
root <== inclusionProof.root;
// Dummy square to prevent tampering signalHash.
signal signalHashSquared;
signalHashSquared <== signalHash * signalHash;
nullifierHash <== calculateNullifierHash.out;
}
component main {public [signalHash, externalNullifier]} = Semaphore(20);

View File

@@ -0,0 +1,12 @@
import { Circomkit } from "circomkit"
import { readFileSync } from "fs"
import path from "path"
const configFilePath = path.join(__dirname, "../circomkit.json")
const config = JSON.parse(readFileSync(configFilePath, "utf-8"))
// eslint-disable-next-line import/prefer-default-export
export const circomkit = new Circomkit({
...config,
verbose: false
})

View File

@@ -0,0 +1,72 @@
import { derivePublicKey, deriveSecretScalar } from "@zk-kit/eddsa-poseidon"
import { LeanIMT } from "@zk-kit/imt"
import { WitnessTester } from "circomkit"
import { poseidon2 } from "poseidon-lite"
import { circomkit } from "./common"
describe("semaphore", () => {
let circuit: WitnessTester<
["secret", "merkleProofLength", "merkleProofIndices", "merkleProofSiblings", "scope", "message"],
["nullifier", "merkleRoot"]
>
const MAX_DEPTH = 20
const scope = 32
const message = 43
const secret = 1
const publicKey = derivePublicKey(secret)
const leaf = poseidon2(publicKey)
const tree = new LeanIMT((a, b) => poseidon2([a, b]))
tree.insert(leaf)
for (let i = 1; i < 4; i += 1) {
tree.insert(BigInt(i))
}
const { siblings: merkleProofSiblings, index } = tree.generateProof(0)
// The index must be converted to a list of indices, 1 for each tree level.
// The circuit tree depth is 20, so the number of siblings must be 20, even if
// the tree depth is actually 3. The missing siblings can be set to 0, as they
// won't be used to calculate the root in the circuit.
const merkleProofIndices: number[] = []
for (let i = 0; i < MAX_DEPTH; i += 1) {
merkleProofIndices.push((index >> i) & 1)
if (merkleProofSiblings[i] === undefined) {
merkleProofSiblings[i] = BigInt(0)
}
}
const INPUT = {
secret: deriveSecretScalar(secret),
merkleProofLength: tree.depth,
merkleProofIndices,
merkleProofSiblings,
scope,
message
}
const OUTPUT = {
nullifier: poseidon2([scope, deriveSecretScalar(secret)]),
merkleRoot: tree.root
}
before(async () => {
circuit = await circomkit.WitnessTester("semaphore", {
file: "semaphore",
template: "Semaphore",
params: [MAX_DEPTH]
})
})
it("Should calculate the root and the nullifier correctly", async () => {
await circuit.expectPass(INPUT, OUTPUT)
})
})

View File

@@ -1,40 +0,0 @@
pragma circom 2.0.0;
include "../node_modules/circomlib/circuits/poseidon.circom";
include "../node_modules/circomlib/circuits/mux1.circom";
template MerkleTreeInclusionProof(nLevels) {
signal input leaf;
signal input pathIndices[nLevels];
signal input siblings[nLevels];
signal output root;
component poseidons[nLevels];
component mux[nLevels];
signal hashes[nLevels + 1];
hashes[0] <== leaf;
for (var i = 0; i < nLevels; i++) {
pathIndices[i] * (1 - pathIndices[i]) === 0;
poseidons[i] = Poseidon(2);
mux[i] = MultiMux1(2);
mux[i].c[0][0] <== hashes[i];
mux[i].c[0][1] <== siblings[i];
mux[i].c[1][0] <== siblings[i];
mux[i].c[1][1] <== hashes[i];
mux[i].s <== pathIndices[i];
poseidons[i].inputs[0] <== mux[i].out[0];
poseidons[i].inputs[1] <== mux[i].out[1];
hashes[i + 1] <== poseidons[i].out;
}
root <== hashes[nLevels];
}

View File

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

View File

@@ -44,7 +44,7 @@ cp .env.example .env
3. And deploy your contract.
```bash
yarn deploy --semaphore <semaphore-address> --group <group-id> --network goerli
yarn deploy --semaphore <semaphore-address> --group <group-id> --network sepolia
```
> **Note**

View File

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

View File

@@ -1,5 +1,6 @@
import "@nomiclabs/hardhat-ethers"
import "@nomicfoundation/hardhat-chai-matchers"
import "@nomicfoundation/hardhat-ethers"
import "@nomicfoundation/hardhat-verify"
import "@semaphore-protocol/hardhat"
import "@typechain/hardhat"
import { config as dotenvConfig } from "dotenv"
@@ -7,7 +8,6 @@ import "hardhat-gas-reporter"
import { HardhatUserConfig } from "hardhat/config"
import { NetworksUserConfig } from "hardhat/types"
import "solidity-coverage"
import { config } from "./package.json"
import "./tasks/deploy"
dotenvConfig()
@@ -21,11 +21,6 @@ function getNetworks(): NetworksUserConfig {
const infuraApiKey = process.env.INFURA_API_KEY
return {
goerli: {
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
chainId: 5,
accounts
},
sepolia: {
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155111,
@@ -36,13 +31,18 @@ function getNetworks(): NetworksUserConfig {
chainId: 80001,
accounts
},
"optimism-goerli": {
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
chainId: 420,
"optimism-sepolia": {
url: `https://optimism-sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155420,
accounts
},
"arbitrum-sepolia": {
url: "https://sepolia-rollup.arbitrum.io/rpc",
chainId: 421614,
accounts
},
arbitrum: {
url: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
url: "https://arb1.arbitrum.io/rpc",
chainId: 42161,
accounts
}
@@ -50,13 +50,7 @@ function getNetworks(): NetworksUserConfig {
}
const hardhatConfig: HardhatUserConfig = {
solidity: config.solidity,
paths: {
sources: config.paths.contracts,
tests: config.paths.tests,
cache: config.paths.cache,
artifacts: config.paths.build.contracts
},
solidity: "0.8.23",
networks: {
hardhat: {
chainId: 1337
@@ -69,8 +63,13 @@ const hardhatConfig: HardhatUserConfig = {
coinmarketcap: process.env.COINMARKETCAP_API_KEY
},
typechain: {
outDir: config.paths.build.typechain,
target: "ethers-v5"
target: "ethers-v6"
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
},
sourcify: {
enabled: true
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/cli-template-contracts-hardhat",
"version": "3.15.2",
"version": "4.0.0-alpha",
"description": "Semaphore Hardhat template.",
"license": "Unlicense",
"files": [
@@ -20,9 +20,8 @@
"scripts": {
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
"compile": "hardhat compile",
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
"deploy": "yarn compile && hardhat deploy",
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
"test": "hardhat test",
"test:report-gas": "REPORT_GAS=true hardhat test",
"test:coverage": "hardhat coverage",
"typechain": "hardhat typechain",
@@ -31,48 +30,37 @@
"devDependencies": {
"@ethersproject/abi": "^5.4.7",
"@ethersproject/providers": "^5.4.7",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.3",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@semaphore-protocol/group": "3.15.2",
"@semaphore-protocol/hardhat": "3.15.2",
"@semaphore-protocol/identity": "3.15.2",
"@semaphore-protocol/proof": "3.15.2",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@semaphore-protocol/group": "4.0.0-alpha",
"@semaphore-protocol/hardhat": "4.0.0-alpha",
"@semaphore-protocol/identity": "4.0.0-alpha",
"@semaphore-protocol/proof": "4.0.0-alpha",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
"@types/download": "^8.0.1",
"@types/mocha": "^9.1.0",
"@types/node": ">=12.0.0",
"@types/node": "^20",
"chai": "^4.2.0",
"dotenv": "^16.0.3",
"download": "^8.0.0",
"ethers": "^5.4.7",
"hardhat": "^2.11.0",
"ethers": "^6.4.0",
"hardhat": "^2.19.4",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.8.1",
"ts-node": ">=8.0.0",
"typechain": "^8.1.0",
"typescript": ">=4.5.0"
"ts-node": "^10.9.2",
"typechain": "^8.3.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@semaphore-protocol/contracts": "3.15.2"
"@semaphore-protocol/contracts": "4.0.0-alpha"
},
"config": {
"solidity": {
"version": "0.8.4"
},
"paths": {
"contracts": "./contracts",
"tests": "./test",
"cache": "./cache",
"build": {
"snark-artifacts": "./build/snark-artifacts",
"contracts": "./build/contracts",
"typechain": "./build/typechain"
}
}
}
},
"stableVersion": "3.15.2"
}

View File

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

View File

@@ -10,7 +10,7 @@ task("deploy", "Deploy a Feedback contract")
logs
})
semaphoreAddress = semaphore.address
semaphoreAddress = await semaphore.getAddress()
}
if (!groupId) {
@@ -21,10 +21,8 @@ task("deploy", "Deploy a Feedback contract")
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
await feedbackContract.deployed()
if (logs) {
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
console.info(`Feedback contract has been deployed to: ${await feedbackContract.getAddress()}`)
}
return feedbackContract

View File

@@ -2,18 +2,17 @@ import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import { formatBytes32String } from "ethers/lib/utils"
import { encodeBytes32String } from "ethers"
import { run } from "hardhat"
// @ts-ignore: typechain folder will be generated after contracts compilation
import { Feedback } from "../build/typechain"
import { config } from "../package.json"
describe("Feedback", () => {
let feedbackContract: Feedback
let semaphoreContract: string
const groupId = "42"
const group = new Group(groupId)
const group = new Group()
const users: Identity[] = []
before(async () => {
@@ -21,7 +20,7 @@ describe("Feedback", () => {
logs: false
})
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address })
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: await semaphore.getAddress() })
semaphoreContract = semaphore
users.push(new Identity())
@@ -43,27 +42,30 @@ describe("Feedback", () => {
})
describe("# sendFeedback", () => {
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
it("Should allow users to send feedback anonymously", async () => {
const feedback = formatBytes32String("Hello World")
const feedback = encodeBytes32String("Hello World")
const fullProof = await generateProof(users[1], group, groupId, feedback, {
wasmFilePath,
zkeyFilePath
})
const fullProof = await generateProof(users[1], group, feedback, groupId)
const transaction = feedbackContract.sendFeedback(
feedback,
fullProof.merkleTreeDepth,
fullProof.merkleTreeRoot,
fullProof.nullifierHash,
fullProof.nullifier,
feedback,
fullProof.proof
)
await expect(transaction)
.to.emit(semaphoreContract, "ProofVerified")
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
.to.emit(semaphoreContract, "ProofValidated")
.withArgs(
groupId,
fullProof.merkleTreeDepth,
fullProof.merkleTreeRoot,
fullProof.nullifier,
fullProof.message,
groupId,
fullProof.proof
)
})
})
})

View File

@@ -1,8 +1,8 @@
DEFAULT_NETWORK=localhost
INFURA_API_KEY=
ETHEREUM_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
FEEDBACK_CONTRACT_ADDRESS=0x5fc8d32690cc91d4c39d9d3abcbd16989f875707
SEMAPHORE_CONTRACT_ADDRESS=0xdc64a140aa3e981100a9beca4e685f962f0cf6c9
FEEDBACK_CONTRACT_ADDRESS=0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9
SEMAPHORE_CONTRACT_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
OPENZEPPELIN_AUTOTASK_WEBHOOK=
GROUP_ID=42
REPORT_GAS=false

View File

@@ -52,7 +52,11 @@ node_modules/
# Generate output
dist
build
# Hardhat files
artifacts
cache
typechain-types
# Next.js
.next/

View File

@@ -25,7 +25,7 @@ yarn dev
1. Go to the `apps/contracts` directory and deploy your contract:
```bash
yarn deploy --semaphore <semaphore-address> --group <group-id> --network arbitrum-goerli
yarn deploy --semaphore <semaphore-address> --group <group-id> --network arbitrum-sepolia
```
2. Update your `.env` file with your new contract address, the group id and the semaphore contract address.

View File

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

View File

@@ -1,18 +1,16 @@
import "@nomicfoundation/hardhat-chai-matchers"
import "@nomiclabs/hardhat-ethers"
import "@nomiclabs/hardhat-etherscan"
import "@nomicfoundation/hardhat-ethers"
import "@nomicfoundation/hardhat-verify"
import "@semaphore-protocol/hardhat"
import "@typechain/hardhat"
import { config as dotenvConfig } from "dotenv"
import "hardhat-gas-reporter"
import { HardhatUserConfig } from "hardhat/config"
import { NetworksUserConfig } from "hardhat/types"
import { resolve } from "path"
import "solidity-coverage"
import { config } from "./package.json"
import "./tasks/deploy"
dotenvConfig({ path: resolve(__dirname, "../../.env") })
dotenvConfig()
function getNetworks(): NetworksUserConfig {
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
@@ -23,11 +21,6 @@ function getNetworks(): NetworksUserConfig {
const infuraApiKey = process.env.INFURA_API_KEY
return {
goerli: {
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
chainId: 5,
accounts
},
sepolia: {
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155111,
@@ -38,14 +31,14 @@ function getNetworks(): NetworksUserConfig {
chainId: 80001,
accounts
},
"optimism-goerli": {
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
chainId: 420,
"optimism-sepolia": {
url: `https://optimism-sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155420,
accounts
},
"arbitrum-goerli": {
url: "https://goerli-rollup.arbitrum.io/rpc",
chainId: 421613,
"arbitrum-sepolia": {
url: "https://sepolia-rollup.arbitrum.io/rpc",
chainId: 421614,
accounts
},
arbitrum: {
@@ -57,13 +50,7 @@ function getNetworks(): NetworksUserConfig {
}
const hardhatConfig: HardhatUserConfig = {
solidity: config.solidity,
paths: {
sources: config.paths.contracts,
tests: config.paths.tests,
cache: config.paths.cache,
artifacts: config.paths.build.contracts
},
solidity: "0.8.23",
networks: {
hardhat: {
chainId: 1337
@@ -76,11 +63,13 @@ const hardhatConfig: HardhatUserConfig = {
coinmarketcap: process.env.COINMARKETCAP_API_KEY
},
typechain: {
outDir: config.paths.build.typechain,
target: "ethers-v5"
target: "ethers-v6"
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
},
sourcify: {
enabled: true
}
}

View File

@@ -6,55 +6,39 @@
"scripts": {
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
"compile": "hardhat compile",
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
"deploy": "yarn compile && hardhat deploy",
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
"test": "hardhat test",
"test:report-gas": "REPORT_GAS=true hardhat test",
"test:coverage": "hardhat coverage",
"typechain": "hardhat typechain",
"lint": "solhint 'contracts/**/*.sol'"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@semaphore-protocol/group": "3.15.2",
"@semaphore-protocol/hardhat": "3.15.2",
"@semaphore-protocol/identity": "3.15.2",
"@semaphore-protocol/proof": "3.15.2",
"@typechain/ethers-v5": "^10.0.0",
"@typechain/hardhat": "^6.0.0",
"@types/chai": "^4.3.1",
"@types/download": "^8.0.1",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.3",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@semaphore-protocol/group": "4.0.0-alpha",
"@semaphore-protocol/hardhat": "4.0.0-alpha",
"@semaphore-protocol/identity": "4.0.0-alpha",
"@semaphore-protocol/proof": "4.0.0-alpha",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
"@types/mocha": "^9.1.1",
"chai": "^4.2.0",
"dotenv": "^14.3.2",
"download": "^8.0.0",
"ethers": "^5.0.0",
"hardhat": "^2.8.4",
"ethers": "^6.4.0",
"hardhat": "^2.19.4",
"hardhat-gas-reporter": "^1.0.8",
"prettier-plugin-solidity": "^1.0.0-beta.19",
"prettier-plugin-solidity": "^1.3.1",
"solhint": "^3.3.6",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.21",
"typechain": "^8.0.0"
"solidity-coverage": "^0.8.0",
"typechain": "^8.3.0"
},
"dependencies": {
"@semaphore-protocol/contracts": "3.15.2"
},
"config": {
"solidity": {
"version": "0.8.4"
},
"paths": {
"contracts": "./contracts",
"tests": "./test",
"cache": "./cache",
"build": {
"snark-artifacts": "./build/snark-artifacts",
"contracts": "./build/contracts",
"typechain": "./build/typechain"
}
}
"@semaphore-protocol/contracts": "4.0.0-alpha"
}
}

View File

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

View File

@@ -10,7 +10,7 @@ task("deploy", "Deploy a Feedback contract")
logs
})
semaphoreAddress = semaphore.address
semaphoreAddress = await semaphore.getAddress()
}
if (!groupId) {
@@ -21,10 +21,8 @@ task("deploy", "Deploy a Feedback contract")
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
await feedbackContract.deployed()
if (logs) {
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
console.info(`Feedback contract has been deployed to: ${await feedbackContract.getAddress()}`)
}
return feedbackContract

View File

@@ -2,18 +2,17 @@ import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import { formatBytes32String } from "ethers/lib/utils"
import { encodeBytes32String } from "ethers"
import { run } from "hardhat"
// @ts-ignore: typechain folder will be generated after contracts compilation
import { Feedback } from "../build/typechain"
import { config } from "../package.json"
describe("Feedback", () => {
let feedbackContract: Feedback
let semaphoreContract: string
const groupId = "42"
const group = new Group(groupId)
const group = new Group()
const users: Identity[] = []
before(async () => {
@@ -21,7 +20,7 @@ describe("Feedback", () => {
logs: false
})
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: semaphore.address })
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: await semaphore.getAddress() })
semaphoreContract = semaphore
users.push(new Identity())
@@ -43,27 +42,30 @@ describe("Feedback", () => {
})
describe("# sendFeedback", () => {
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
it("Should allow users to send feedback anonymously", async () => {
const feedback = formatBytes32String("Hello World")
const feedback = encodeBytes32String("Hello World")
const fullProof = await generateProof(users[1], group, groupId, feedback, {
wasmFilePath,
zkeyFilePath
})
const fullProof = await generateProof(users[1], group, feedback, groupId)
const transaction = feedbackContract.sendFeedback(
feedback,
fullProof.merkleTreeDepth,
fullProof.merkleTreeRoot,
fullProof.nullifierHash,
fullProof.nullifier,
feedback,
fullProof.proof
)
await expect(transaction)
.to.emit(semaphoreContract, "ProofVerified")
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
.to.emit(semaphoreContract, "ProofValidated")
.withArgs(
groupId,
fullProof.merkleTreeDepth,
fullProof.merkleTreeRoot,
fullProof.nullifier,
fullProof.message,
groupId,
fullProof.proof
)
})
})
})

View File

@@ -6,8 +6,8 @@
{
"inputs": [
{
"internalType": "address",
"name": "semaphoreAddress",
"internalType": "contract ISemaphore",
"name": "_semaphore",
"type": "address"
},
{
@@ -62,7 +62,7 @@
"inputs": [
{
"internalType": "uint256",
"name": "feedback",
"name": "merkleTreeDepth",
"type": "uint256"
},
{
@@ -72,7 +72,12 @@
},
{
"internalType": "uint256",
"name": "nullifierHash",
"name": "nullifier",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feedback",
"type": "uint256"
},
{
@@ -87,8 +92,8 @@
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b506040516106e13803806106e18339818101604052810190610032919061013c565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639c1121416001546014306040518463ffffffff1660e01b81526004016100d9939291906101a5565b600060405180830381600087803b1580156100f357600080fd5b505af1158015610107573d6000803e3d6000fd5b505050505050610258565b6000815190506101218161022a565b92915050565b60008151905061013681610241565b92915050565b6000806040838503121561014f57600080fd5b600061015d85828601610112565b925050602061016e85828601610127565b9150509250929050565b610181816101dc565b82525050565b61019081610218565b82525050565b61019f8161020e565b82525050565b60006060820190506101ba6000830186610196565b6101c76020830185610187565b6101d46040830184610178565b949350505050565b60006101e7826101ee565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102238261020e565b9050919050565b610233816101dc565b811461023e57600080fd5b50565b61024a8161020e565b811461025557600080fd5b50565b61047a806102676000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033",
"bytecode": "0x608060405234801561001057600080fd5b5060405161072b38038061072b833981810160405281019061003291906101ba565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c96e71fb600154306040518363ffffffff1660e01b81526004016100d6929190610218565b600060405180830381600087803b1580156100f057600080fd5b505af1158015610104573d6000803e3d6000fd5b505050505050610241565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061013f82610114565b9050919050565b600061015182610134565b9050919050565b61016181610146565b811461016c57600080fd5b50565b60008151905061017e81610158565b92915050565b6000819050919050565b61019781610184565b81146101a257600080fd5b50565b6000815190506101b48161018e565b92915050565b600080604083850312156101d1576101d061010f565b5b60006101df8582860161016f565b92505060206101f0858286016101a5565b9150509250929050565b61020381610184565b82525050565b61021281610134565b82525050565b600060408201905061022d60008301856101fa565b61023a6020830184610209565b9392505050565b6104db806102506000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b60405161006691906102a2565b60405180910390f35b6100896004803603810190610084919061031f565b6100e9565b005b61009361018b565b6040516100a091906103aa565b60405180910390f35b6100c360048036038101906100be91906103c5565b610191565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632694b47660015487878787600154886040518863ffffffff1660e01b8152600401610152979695949392919061040c565b600060405180830381600087803b15801561016c57600080fd5b505af1158015610180573d6000803e3d6000fd5b505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101ee92919061047c565b600060405180830381600087803b15801561020857600080fd5b505af115801561021c573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061026861026361025e84610223565b610243565b610223565b9050919050565b600061027a8261024d565b9050919050565b600061028c8261026f565b9050919050565b61029c81610281565b82525050565b60006020820190506102b76000830184610293565b92915050565b600080fd5b6000819050919050565b6102d5816102c2565b81146102e057600080fd5b50565b6000813590506102f2816102cc565b92915050565b600080fd5b600081905082602060080282011115610319576103186102f8565b5b92915050565b6000806000806000610180868803121561033c5761033b6102bd565b5b600061034a888289016102e3565b955050602061035b888289016102e3565b945050604061036c888289016102e3565b935050606061037d888289016102e3565b925050608061038e888289016102fd565b9150509295509295909350565b6103a4816102c2565b82525050565b60006020820190506103bf600083018461039b565b92915050565b6000602082840312156103db576103da6102bd565b5b60006103e9848285016102e3565b91505092915050565b82818337505050565b61040861010083836103f2565b5050565b60006101c082019050610422600083018a61039b565b61042f602083018961039b565b61043c604083018861039b565b610449606083018761039b565b610456608083018661039b565b61046360a083018561039b565b61047060c08301846103fb565b98975050505050505050565b6000604082019050610491600083018561039b565b61049e602083018461039b565b939250505056fea26469706673582212204e55122b6afd94ae34af34764263b41bcb463e4fc817ea7ab795705071f8e56f64736f6c63430008170033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b60405161006691906102a2565b60405180910390f35b6100896004803603810190610084919061031f565b6100e9565b005b61009361018b565b6040516100a091906103aa565b60405180910390f35b6100c360048036038101906100be91906103c5565b610191565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632694b47660015487878787600154886040518863ffffffff1660e01b8152600401610152979695949392919061040c565b600060405180830381600087803b15801561016c57600080fd5b505af1158015610180573d6000803e3d6000fd5b505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101ee92919061047c565b600060405180830381600087803b15801561020857600080fd5b505af115801561021c573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061026861026361025e84610223565b610243565b610223565b9050919050565b600061027a8261024d565b9050919050565b600061028c8261026f565b9050919050565b61029c81610281565b82525050565b60006020820190506102b76000830184610293565b92915050565b600080fd5b6000819050919050565b6102d5816102c2565b81146102e057600080fd5b50565b6000813590506102f2816102cc565b92915050565b600080fd5b600081905082602060080282011115610319576103186102f8565b5b92915050565b6000806000806000610180868803121561033c5761033b6102bd565b5b600061034a888289016102e3565b955050602061035b888289016102e3565b945050604061036c888289016102e3565b935050606061037d888289016102e3565b925050608061038e888289016102fd565b9150509295509295909350565b6103a4816102c2565b82525050565b60006020820190506103bf600083018461039b565b92915050565b6000602082840312156103db576103da6102bd565b5b60006103e9848285016102e3565b91505092915050565b82818337505050565b61040861010083836103f2565b5050565b60006101c082019050610422600083018a61039b565b61042f602083018961039b565b61043c604083018861039b565b610449606083018761039b565b610456608083018661039b565b61046360a083018561039b565b61047060c08301846103fb565b98975050505050505050565b6000604082019050610491600083018561039b565b61049e602083018461039b565b939250505056fea26469706673582212204e55122b6afd94ae34af34764263b41bcb463e4fc817ea7ab795705071f8e56f64736f6c63430008170033",
"linkReferences": {},
"deployedLinkReferences": {}
}

View File

@@ -30,15 +30,6 @@ const nextConfig = withPWA({
SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS,
OPENZEPPELIN_AUTOTASK_WEBHOOK: process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK,
GROUP_ID: process.env.GROUP_ID
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
fs: false
}
}
return config
}
})

View File

@@ -10,14 +10,14 @@
},
"dependencies": {
"@next/font": "13.0.3",
"@semaphore-protocol/data": "3.15.2",
"@semaphore-protocol/group": "3.15.2",
"@semaphore-protocol/identity": "3.15.2",
"@semaphore-protocol/proof": "3.15.2",
"@semaphore-protocol/data": "4.0.0-alpha",
"@semaphore-protocol/group": "4.0.0-alpha",
"@semaphore-protocol/identity": "4.0.0-alpha",
"@semaphore-protocol/proof": "4.0.0-alpha",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.8",
"dotenv": "^16.0.3",
"ethers": "^5.7.2",
"ethers": "^6.4.0",
"next": "13.0.3",
"next-pwa": "^5.6.0",
"react": "18.2.0",

View File

@@ -1,5 +1,5 @@
import { SemaphoreEthers } from "@semaphore-protocol/data"
import { BigNumber, utils } from "ethers"
import { decodeBytes32String, toBeHex } from "ethers"
import getNextConfig from "next/config"
import { useCallback, useState } from "react"
import { SemaphoreContextType } from "../context/SemaphoreContext"
@@ -19,7 +19,7 @@ export default function useSemaphore(): SemaphoreContextType {
const members = await semaphore.getGroupMembers(env.GROUP_ID)
setUsers(members)
setUsers(members.map((member) => member.toString()))
}, [])
const addUser = useCallback(
@@ -34,9 +34,9 @@ export default function useSemaphore(): SemaphoreContextType {
address: env.SEMAPHORE_CONTRACT_ADDRESS
})
const proofs = await semaphore.getGroupVerifiedProofs(env.GROUP_ID)
const proofs = await semaphore.getGroupValidatedProofs(env.GROUP_ID)
setFeedback(proofs.map(({ signal }: any) => utils.parseBytes32String(BigNumber.from(signal).toHexString())))
setFeedback(proofs.map(({ message }: any) => decodeBytes32String(toBeHex(message, 32))))
}, [])
const addFeedback = useCallback(

View File

@@ -1,4 +1,4 @@
import { Contract, providers, Wallet } from "ethers"
import { Contract, InfuraProvider, JsonRpcProvider, Wallet } from "ethers"
import type { NextApiRequest, NextApiResponse } from "next"
import Feedback from "../../../contract-artifacts/Feedback.json"
@@ -25,17 +25,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS
const provider =
ethereumNetwork === "localhost"
? new providers.JsonRpcProvider()
: new providers.InfuraProvider(ethereumNetwork, infuraApiKey)
ethereumNetwork === "localhost" ? new JsonRpcProvider() : new InfuraProvider(ethereumNetwork, infuraApiKey)
const signer = new Wallet(ethereumPrivateKey, provider)
const contract = new Contract(contractAddress, Feedback.abi, signer)
const { feedback, merkleTreeRoot, nullifierHash, proof } = req.body
const { feedback, merkleTreeDepth, merkleTreeRoot, nullifier, proof } = req.body
try {
const transaction = await contract.sendFeedback(feedback, merkleTreeRoot, nullifierHash, proof)
const transaction = await contract.sendFeedback(merkleTreeDepth, merkleTreeRoot, nullifier, feedback, proof)
await transaction.wait()

View File

@@ -1,4 +1,4 @@
import { Contract, providers, Wallet } from "ethers"
import { Contract, InfuraProvider, JsonRpcProvider, Wallet } from "ethers"
import type { NextApiRequest, NextApiResponse } from "next"
import Feedback from "../../../contract-artifacts/Feedback.json"
@@ -25,9 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS
const provider =
ethereumNetwork === "localhost"
? new providers.JsonRpcProvider()
: new providers.InfuraProvider(ethereumNetwork, infuraApiKey)
ethereumNetwork === "localhost" ? new JsonRpcProvider() : new InfuraProvider(ethereumNetwork, infuraApiKey)
const signer = new Wallet(ethereumPrivateKey, provider)
const contract = new Contract(contractAddress, Feedback.abi, signer)

View File

@@ -17,14 +17,14 @@ export default function GroupsPage() {
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const identityString = localStorage.getItem("identity")
const privateKey = localStorage.getItem("identity")
if (!identityString) {
if (!privateKey) {
router.push("/")
return
}
setIdentity(new Identity(identityString))
setIdentity(new Identity(privateKey))
}, [])
useEffect(() => {
@@ -75,7 +75,7 @@ export default function GroupsPage() {
setLoading(false)
}, [_identity])
const userHasJoined = useCallback((identity: Identity) => _users.includes(identity.commitment.toString()), [_users])
const userHasJoined = useCallback((identity: Identity) => _users.includes(identity.commitment), [_users])
return (
<>

View File

@@ -10,10 +10,10 @@ export default function IdentitiesPage() {
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const identityString = localStorage.getItem("identity")
const privateKey = localStorage.getItem("identity")
if (identityString) {
const identity = new Identity(identityString)
if (privateKey) {
const identity = new Identity(privateKey)
setIdentity(identity)
@@ -28,7 +28,7 @@ export default function IdentitiesPage() {
setIdentity(identity)
localStorage.setItem("identity", identity.toString())
localStorage.setItem("identity", identity.privateKey.toString())
setLogs("Your new Semaphore identity was just created 🎉")
}, [])
@@ -68,8 +68,7 @@ export default function IdentitiesPage() {
{_identity ? (
<div>
<div className="box">
<p className="box-text">Trapdoor: {_identity.trapdoor.toString()}</p>
<p className="box-text">Nullifier: {_identity.nullifier.toString()}</p>
<p className="box-text">Private Key: {_identity.privateKey.toString()}</p>
<p className="box-text">Commitment: {_identity.commitment.toString()}</p>
</div>
</div>

View File

@@ -1,7 +1,7 @@
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof } from "@semaphore-protocol/proof"
import { BigNumber, utils } from "ethers"
import { encodeBytes32String } from "ethers"
import getNextConfig from "next/config"
import { useRouter } from "next/router"
import { useCallback, useContext, useEffect, useState } from "react"
@@ -20,14 +20,14 @@ export default function ProofsPage() {
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const identityString = localStorage.getItem("identity")
const privateKey = localStorage.getItem("identity")
if (!identityString) {
if (!privateKey) {
router.push("/")
return
}
setIdentity(new Identity(identityString))
setIdentity(new Identity(privateKey))
}, [])
useEffect(() => {
@@ -51,15 +51,15 @@ export default function ProofsPage() {
try {
const group = new Group(env.GROUP_ID)
const signal = BigNumber.from(utils.formatBytes32String(feedback)).toString()
const message = encodeBytes32String(feedback)
group.addMembers(_users)
const { proof, merkleTreeRoot, nullifierHash } = await generateProof(
const { proof, merkleTreeDepth, merkleTreeRoot, nullifier } = await generateProof(
_identity,
group,
env.GROUP_ID,
signal
message,
env.GROUP_ID
)
let response: any
@@ -72,7 +72,7 @@ export default function ProofsPage() {
abi: Feedback.abi,
address: env.FEEDBACK_CONTRACT_ADDRESS,
functionName: "sendFeedback",
functionParameters: [signal, merkleTreeRoot, nullifierHash, proof]
functionParameters: [merkleTreeDepth, merkleTreeRoot, nullifier, message, proof]
})
})
} else {
@@ -80,9 +80,10 @@ export default function ProofsPage() {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
feedback: signal,
feedback: message,
merkleTreeDepth,
merkleTreeRoot,
nullifierHash,
nullifier,
proof
})
})

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/cli-template-monorepo-ethers",
"version": "3.15.2",
"version": "4.0.0-alpha",
"description": "Semaphore Hardhat + Next.js + SemaphoreEthers template.",
"license": "Unlicense",
"files": [
@@ -22,14 +22,13 @@
"lint": "eslint . --ext .js,.ts",
"prettier": "prettier -c .",
"prettier:write": "prettier -w .",
"copy:contract-artifacts": "ts-node scripts/copy-contract-artifacts.ts",
"prepublish": "tar -czf files.tgz .gitignore .yarn .yarnrc.yml apps"
},
"workspaces": [
"apps/*"
],
"devDependencies": {
"@types/node": "^17.0.9",
"@types/node": "^20",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"eslint": "^8.2.0",
@@ -40,5 +39,6 @@
"prettier": "^2.5.1",
"ts-node": "^10.8.1",
"typescript": "^4.7.3"
}
},
"stableVersion": "3.15.2"
}

View File

@@ -1,15 +0,0 @@
import * as fs from "fs"
async function main() {
const contractArtifactsPath = "apps/contracts/build/contracts/contracts/Feedback.sol"
const webAppArtifactsPath = "apps/web-app/contract-artifacts"
await fs.promises.copyFile(`${contractArtifactsPath}/Feedback.json`, `${webAppArtifactsPath}/Feedback.json`)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@@ -1,8 +1,8 @@
DEFAULT_NETWORK=localhost
INFURA_API_KEY=
ETHEREUM_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
FEEDBACK_CONTRACT_ADDRESS=0x5fc8d32690cc91d4c39d9d3abcbd16989f875707
SEMAPHORE_CONTRACT_ADDRESS=0xdc64a140aa3e981100a9beca4e685f962f0cf6c9
FEEDBACK_CONTRACT_ADDRESS=0xcf7ed3acca5a467e9e704c703e8d87f634fb0fc9
SEMAPHORE_CONTRACT_ADDRESS=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
OPENZEPPELIN_AUTOTASK_WEBHOOK=
GROUP_ID=42
REPORT_GAS=false

View File

@@ -52,7 +52,11 @@ node_modules/
# Generate output
dist
build
# Hardhat files
artifacts
cache
typechain-types
# Next.js
.next/

View File

@@ -25,7 +25,7 @@ yarn dev
1. Go to the `apps/contracts` directory and deploy your contract:
```bash
yarn deploy --semaphore <semaphore-address> --group <group-id> --network arbitrum-goerli
yarn deploy --semaphore <semaphore-address> --group <group-id> --network arbitrum-sepolia
```
2. Update your `.env` file with your new contract address, the group id and the semaphore contract address.

View File

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

View File

@@ -1,18 +1,16 @@
import "@nomicfoundation/hardhat-chai-matchers"
import "@nomiclabs/hardhat-ethers"
import "@nomiclabs/hardhat-etherscan"
import "@nomicfoundation/hardhat-ethers"
import "@nomicfoundation/hardhat-verify"
import "@semaphore-protocol/hardhat"
import "@typechain/hardhat"
import { config as dotenvConfig } from "dotenv"
import "hardhat-gas-reporter"
import { HardhatUserConfig } from "hardhat/config"
import { NetworksUserConfig } from "hardhat/types"
import { resolve } from "path"
import "solidity-coverage"
import { config } from "./package.json"
import "./tasks/deploy"
dotenvConfig({ path: resolve(__dirname, "../../.env") })
dotenvConfig()
function getNetworks(): NetworksUserConfig {
if (!process.env.INFURA_API_KEY || !process.env.ETHEREUM_PRIVATE_KEY) {
@@ -23,11 +21,6 @@ function getNetworks(): NetworksUserConfig {
const infuraApiKey = process.env.INFURA_API_KEY
return {
goerli: {
url: `https://goerli.infura.io/v3/${infuraApiKey}`,
chainId: 5,
accounts
},
sepolia: {
url: `https://sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155111,
@@ -38,14 +31,14 @@ function getNetworks(): NetworksUserConfig {
chainId: 80001,
accounts
},
"optimism-goerli": {
url: `https://optimism-goerli.infura.io/v3/${infuraApiKey}`,
chainId: 420,
"optimism-sepolia": {
url: `https://optimism-sepolia.infura.io/v3/${infuraApiKey}`,
chainId: 11155420,
accounts
},
"arbitrum-goerli": {
url: "https://goerli-rollup.arbitrum.io/rpc",
chainId: 421613,
"arbitrum-sepolia": {
url: "https://sepolia-rollup.arbitrum.io/rpc",
chainId: 421614,
accounts
},
arbitrum: {
@@ -57,13 +50,7 @@ function getNetworks(): NetworksUserConfig {
}
const hardhatConfig: HardhatUserConfig = {
solidity: config.solidity,
paths: {
sources: config.paths.contracts,
tests: config.paths.tests,
cache: config.paths.cache,
artifacts: config.paths.build.contracts
},
solidity: "0.8.23",
networks: {
hardhat: {
chainId: 1337
@@ -76,11 +63,13 @@ const hardhatConfig: HardhatUserConfig = {
coinmarketcap: process.env.COINMARKETCAP_API_KEY
},
typechain: {
outDir: config.paths.build.typechain,
target: "ethers-v5"
target: "ethers-v6"
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
},
sourcify: {
enabled: true
}
}

View File

@@ -6,55 +6,39 @@
"scripts": {
"dev": "hardhat node & yarn compile && yarn deploy --network localhost",
"compile": "hardhat compile",
"download:snark-artifacts": "hardhat run scripts/download-snark-artifacts.ts",
"deploy": "yarn compile && hardhat deploy",
"test": "hardhat run scripts/download-snark-artifacts.ts && hardhat test",
"test": "hardhat test",
"test:report-gas": "REPORT_GAS=true hardhat test",
"test:coverage": "hardhat coverage",
"typechain": "hardhat typechain",
"lint": "solhint 'contracts/**/*.sol'"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^1.0.5",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@semaphore-protocol/group": "3.15.2",
"@semaphore-protocol/hardhat": "3.15.2",
"@semaphore-protocol/identity": "3.15.2",
"@semaphore-protocol/proof": "3.15.2",
"@typechain/ethers-v5": "^10.0.0",
"@typechain/hardhat": "^6.0.0",
"@types/chai": "^4.3.1",
"@types/download": "^8.0.1",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.3",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@semaphore-protocol/group": "4.0.0-alpha",
"@semaphore-protocol/hardhat": "4.0.0-alpha",
"@semaphore-protocol/identity": "4.0.0-alpha",
"@semaphore-protocol/proof": "4.0.0-alpha",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
"@types/mocha": "^9.1.1",
"chai": "^4.2.0",
"dotenv": "^14.3.2",
"download": "^8.0.0",
"ethers": "^5.0.0",
"hardhat": "^2.8.4",
"ethers": "^6.4.0",
"hardhat": "^2.19.4",
"hardhat-gas-reporter": "^1.0.8",
"prettier-plugin-solidity": "^1.0.0-beta.19",
"prettier-plugin-solidity": "^1.3.1",
"solhint": "^3.3.6",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.21",
"typechain": "^8.0.0"
"solidity-coverage": "^0.8.0",
"typechain": "^8.3.0"
},
"dependencies": {
"@semaphore-protocol/contracts": "3.15.2"
},
"config": {
"solidity": {
"version": "0.8.4"
},
"paths": {
"contracts": "./contracts",
"tests": "./test",
"cache": "./cache",
"build": {
"snark-artifacts": "./build/snark-artifacts",
"contracts": "./build/contracts",
"typechain": "./build/typechain"
}
}
"@semaphore-protocol/contracts": "4.0.0-alpha"
}
}

View File

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

View File

@@ -10,7 +10,7 @@ task("deploy", "Deploy a Feedback contract")
logs
})
semaphoreAddress = semaphore.address
semaphoreAddress = await semaphore.getAddress()
}
if (!groupId) {
@@ -21,10 +21,8 @@ task("deploy", "Deploy a Feedback contract")
const feedbackContract = await FeedbackFactory.deploy(semaphoreAddress, groupId)
await feedbackContract.deployed()
if (logs) {
console.info(`Feedback contract has been deployed to: ${feedbackContract.address}`)
console.info(`Feedback contract has been deployed to: ${await feedbackContract.getAddress()}`)
}
return feedbackContract

View File

@@ -2,18 +2,17 @@ import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof } from "@semaphore-protocol/proof"
import { expect } from "chai"
import { formatBytes32String } from "ethers/lib/utils"
import { encodeBytes32String } from "ethers"
import { run } from "hardhat"
// @ts-ignore: typechain folder will be generated after contracts compilation
import { Feedback } from "../build/typechain"
import { config } from "../package.json"
describe("Feedback", () => {
let feedbackContract: Feedback
let semaphoreContract: string
const groupId = "42"
const group = new Group(groupId)
const group = new Group()
const users: Identity[] = []
before(async () => {
@@ -21,11 +20,7 @@ describe("Feedback", () => {
logs: false
})
feedbackContract = await run("deploy", {
logs: false,
group: groupId,
semaphore: semaphore.address
})
feedbackContract = await run("deploy", { logs: false, group: groupId, semaphore: await semaphore.getAddress() })
semaphoreContract = semaphore
users.push(new Identity())
@@ -47,27 +42,30 @@ describe("Feedback", () => {
})
describe("# sendFeedback", () => {
const wasmFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.wasm`
const zkeyFilePath = `${config.paths.build["snark-artifacts"]}/semaphore.zkey`
it("Should allow users to send feedback anonymously", async () => {
const feedback = formatBytes32String("Hello World")
const feedback = encodeBytes32String("Hello World")
const fullProof = await generateProof(users[1], group, groupId, feedback, {
wasmFilePath,
zkeyFilePath
})
const fullProof = await generateProof(users[1], group, feedback, groupId)
const transaction = feedbackContract.sendFeedback(
feedback,
fullProof.merkleTreeDepth,
fullProof.merkleTreeRoot,
fullProof.nullifierHash,
fullProof.nullifier,
feedback,
fullProof.proof
)
await expect(transaction)
.to.emit(semaphoreContract, "ProofVerified")
.withArgs(groupId, fullProof.merkleTreeRoot, fullProof.nullifierHash, groupId, fullProof.signal)
.to.emit(semaphoreContract, "ProofValidated")
.withArgs(
groupId,
fullProof.merkleTreeDepth,
fullProof.merkleTreeRoot,
fullProof.nullifier,
fullProof.message,
groupId,
fullProof.proof
)
})
})
})

View File

@@ -6,8 +6,8 @@
{
"inputs": [
{
"internalType": "address",
"name": "semaphoreAddress",
"internalType": "contract ISemaphore",
"name": "_semaphore",
"type": "address"
},
{
@@ -62,7 +62,7 @@
"inputs": [
{
"internalType": "uint256",
"name": "feedback",
"name": "merkleTreeDepth",
"type": "uint256"
},
{
@@ -72,7 +72,12 @@
},
{
"internalType": "uint256",
"name": "nullifierHash",
"name": "nullifier",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "feedback",
"type": "uint256"
},
{
@@ -87,8 +92,8 @@
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b506040516106e13803806106e18339818101604052810190610032919061013c565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639c1121416001546014306040518463ffffffff1660e01b81526004016100d9939291906101a5565b600060405180830381600087803b1580156100f357600080fd5b505af1158015610107573d6000803e3d6000fd5b505050505050610258565b6000815190506101218161022a565b92915050565b60008151905061013681610241565b92915050565b6000806040838503121561014f57600080fd5b600061015d85828601610112565b925050602061016e85828601610127565b9150509250929050565b610181816101dc565b82525050565b61019081610218565b82525050565b61019f8161020e565b82525050565b60006060820190506101ba6000830186610196565b6101c76020830185610187565b6101d46040830184610178565b949350505050565b60006101e7826101ee565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006102238261020e565b9050919050565b610233816101dc565b811461023e57600080fd5b50565b61024a8161020e565b811461025557600080fd5b50565b61047a806102676000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d253414610051578063a0f44c921461006f578063d18ed1e91461008d578063eed02e4b146100a9575b600080fd5b6100596100c5565b604051610066919061030f565b60405180910390f35b6100776100e9565b604051610084919061032a565b60405180910390f35b6100a760048036038101906100a2919061027c565b6100ef565b005b6100c360048036038101906100be9190610253565b61018e565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16633bc778e3600154858786600154876040518763ffffffff1660e01b81526004016101569695949392919061036e565b600060405180830381600087803b15801561017057600080fd5b505af1158015610184573d6000803e3d6000fd5b5050505050505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101eb929190610345565b600060405180830381600087803b15801561020557600080fd5b505af1158015610219573d6000803e3d6000fd5b5050505050565b60008190508260206008028201111561023857600080fd5b92915050565b60008135905061024d8161042d565b92915050565b60006020828403121561026557600080fd5b60006102738482850161023e565b91505092915050565b600080600080610160858703121561029357600080fd5b60006102a18782880161023e565b94505060206102b28782880161023e565b93505060406102c38782880161023e565b92505060606102d487828801610220565b91505092959194509250565b6102ed610100838361041e565b5050565b6102fa816103fa565b82525050565b610309816103f0565b82525050565b600060208201905061032460008301846102f1565b92915050565b600060208201905061033f6000830184610300565b92915050565b600060408201905061035a6000830185610300565b6103676020830184610300565b9392505050565b60006101a0820190506103846000830189610300565b6103916020830188610300565b61039e6040830187610300565b6103ab6060830186610300565b6103b86080830185610300565b6103c560a08301846102e0565b979650505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006104058261040c565b9050919050565b6000610417826103d0565b9050919050565b82818337600083830152505050565b610436816103f0565b811461044157600080fd5b5056fea26469706673582212204d8dc3161abc759242364c3a754a86e5eb8653092bcdf1e20bd6fcd368e1997664736f6c63430008040033",
"bytecode": "0x608060405234801561001057600080fd5b5060405161072b38038061072b833981810160405281019061003291906101ba565b816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060018190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c96e71fb600154306040518363ffffffff1660e01b81526004016100d6929190610218565b600060405180830381600087803b1580156100f057600080fd5b505af1158015610104573d6000803e3d6000fd5b505050505050610241565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061013f82610114565b9050919050565b600061015182610134565b9050919050565b61016181610146565b811461016c57600080fd5b50565b60008151905061017e81610158565b92915050565b6000819050919050565b61019781610184565b81146101a257600080fd5b50565b6000815190506101b48161018e565b92915050565b600080604083850312156101d1576101d061010f565b5b60006101df8582860161016f565b92505060206101f0858286016101a5565b9150509250929050565b61020381610184565b82525050565b61021281610134565b82525050565b600060408201905061022d60008301856101fa565b61023a6020830184610209565b9392505050565b6104db806102506000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b60405161006691906102a2565b60405180910390f35b6100896004803603810190610084919061031f565b6100e9565b005b61009361018b565b6040516100a091906103aa565b60405180910390f35b6100c360048036038101906100be91906103c5565b610191565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632694b47660015487878787600154886040518863ffffffff1660e01b8152600401610152979695949392919061040c565b600060405180830381600087803b15801561016c57600080fd5b505af1158015610180573d6000803e3d6000fd5b505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101ee92919061047c565b600060405180830381600087803b15801561020857600080fd5b505af115801561021c573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061026861026361025e84610223565b610243565b610223565b9050919050565b600061027a8261024d565b9050919050565b600061028c8261026f565b9050919050565b61029c81610281565b82525050565b60006020820190506102b76000830184610293565b92915050565b600080fd5b6000819050919050565b6102d5816102c2565b81146102e057600080fd5b50565b6000813590506102f2816102cc565b92915050565b600080fd5b600081905082602060080282011115610319576103186102f8565b5b92915050565b6000806000806000610180868803121561033c5761033b6102bd565b5b600061034a888289016102e3565b955050602061035b888289016102e3565b945050604061036c888289016102e3565b935050606061037d888289016102e3565b925050608061038e888289016102fd565b9150509295509295909350565b6103a4816102c2565b82525050565b60006020820190506103bf600083018461039b565b92915050565b6000602082840312156103db576103da6102bd565b5b60006103e9848285016102e3565b91505092915050565b82818337505050565b61040861010083836103f2565b5050565b60006101c082019050610422600083018a61039b565b61042f602083018961039b565b61043c604083018861039b565b610449606083018761039b565b610456608083018661039b565b61046360a083018561039b565b61047060c08301846103fb565b98975050505050505050565b6000604082019050610491600083018561039b565b61049e602083018461039b565b939250505056fea26469706673582212204e55122b6afd94ae34af34764263b41bcb463e4fc817ea7ab795705071f8e56f64736f6c63430008170033",
"deployedBytecode": "0x608060405234801561001057600080fd5b506004361061004c5760003560e01c80637b5d2534146100515780637b85d27a1461006f578063a0f44c921461008b578063eed02e4b146100a9575b600080fd5b6100596100c5565b60405161006691906102a2565b60405180910390f35b6100896004803603810190610084919061031f565b6100e9565b005b61009361018b565b6040516100a091906103aa565b60405180910390f35b6100c360048036038101906100be91906103c5565b610191565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632694b47660015487878787600154886040518863ffffffff1660e01b8152600401610152979695949392919061040c565b600060405180830381600087803b15801561016c57600080fd5b505af1158015610180573d6000803e3d6000fd5b505050505050505050565b60015481565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631783efc3600154836040518363ffffffff1660e01b81526004016101ee92919061047c565b600060405180830381600087803b15801561020857600080fd5b505af115801561021c573d6000803e3d6000fd5b5050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600061026861026361025e84610223565b610243565b610223565b9050919050565b600061027a8261024d565b9050919050565b600061028c8261026f565b9050919050565b61029c81610281565b82525050565b60006020820190506102b76000830184610293565b92915050565b600080fd5b6000819050919050565b6102d5816102c2565b81146102e057600080fd5b50565b6000813590506102f2816102cc565b92915050565b600080fd5b600081905082602060080282011115610319576103186102f8565b5b92915050565b6000806000806000610180868803121561033c5761033b6102bd565b5b600061034a888289016102e3565b955050602061035b888289016102e3565b945050604061036c888289016102e3565b935050606061037d888289016102e3565b925050608061038e888289016102fd565b9150509295509295909350565b6103a4816102c2565b82525050565b60006020820190506103bf600083018461039b565b92915050565b6000602082840312156103db576103da6102bd565b5b60006103e9848285016102e3565b91505092915050565b82818337505050565b61040861010083836103f2565b5050565b60006101c082019050610422600083018a61039b565b61042f602083018961039b565b61043c604083018861039b565b610449606083018761039b565b610456608083018661039b565b61046360a083018561039b565b61047060c08301846103fb565b98975050505050505050565b6000604082019050610491600083018561039b565b61049e602083018461039b565b939250505056fea26469706673582212204e55122b6afd94ae34af34764263b41bcb463e4fc817ea7ab795705071f8e56f64736f6c63430008170033",
"linkReferences": {},
"deployedLinkReferences": {}
}

View File

@@ -30,15 +30,6 @@ const nextConfig = withPWA({
SEMAPHORE_CONTRACT_ADDRESS: process.env.SEMAPHORE_CONTRACT_ADDRESS,
OPENZEPPELIN_AUTOTASK_WEBHOOK: process.env.OPENZEPPELIN_AUTOTASK_WEBHOOK,
GROUP_ID: process.env.GROUP_ID
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
fs: false
}
}
return config
}
})

View File

@@ -10,14 +10,14 @@
},
"dependencies": {
"@next/font": "13.0.3",
"@semaphore-protocol/data": "3.15.2",
"@semaphore-protocol/group": "3.15.2",
"@semaphore-protocol/identity": "3.15.2",
"@semaphore-protocol/proof": "3.15.2",
"@semaphore-protocol/data": "4.0.0-alpha",
"@semaphore-protocol/group": "4.0.0-alpha",
"@semaphore-protocol/identity": "4.0.0-alpha",
"@semaphore-protocol/proof": "4.0.0-alpha",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.8",
"dotenv": "^16.0.3",
"ethers": "^5.7.2",
"ethers": "^6.4.0",
"next": "13.0.3",
"next-pwa": "^5.6.0",
"react": "18.2.0",

View File

@@ -1,5 +1,5 @@
import { SemaphoreSubgraph } from "@semaphore-protocol/data"
import { BigNumber, utils } from "ethers"
import { decodeBytes32String, toBeHex } from "ethers"
import getNextConfig from "next/config"
import { useCallback, useState } from "react"
import { SemaphoreContextType } from "../context/SemaphoreContext"
@@ -31,14 +31,10 @@ export default function useSemaphore(): SemaphoreContextType {
const semaphore = new SemaphoreSubgraph(ethereumNetwork)
const group = await semaphore.getGroup(env.GROUP_ID, {
verifiedProofs: true
validatedProofs: true
})
setFeedback(
group.verifiedProofs!.map(({ signal }: any) =>
utils.parseBytes32String(BigNumber.from(signal).toHexString())
)
)
setFeedback(group.validatedProofs!.map(({ message }: any) => decodeBytes32String(toBeHex(message, 32))))
}, [])
const addFeedback = useCallback(

View File

@@ -1,4 +1,4 @@
import { Contract, providers, Wallet } from "ethers"
import { Contract, InfuraProvider, JsonRpcProvider, Wallet } from "ethers"
import type { NextApiRequest, NextApiResponse } from "next"
import Feedback from "../../../contract-artifacts/Feedback.json"
@@ -25,17 +25,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS
const provider =
ethereumNetwork === "localhost"
? new providers.JsonRpcProvider()
: new providers.InfuraProvider(ethereumNetwork, infuraApiKey)
ethereumNetwork === "localhost" ? new JsonRpcProvider() : new InfuraProvider(ethereumNetwork, infuraApiKey)
const signer = new Wallet(ethereumPrivateKey, provider)
const contract = new Contract(contractAddress, Feedback.abi, signer)
const { feedback, merkleTreeRoot, nullifierHash, proof } = req.body
const { feedback, merkleTreeDepth, merkleTreeRoot, nullifier, proof } = req.body
try {
const transaction = await contract.sendFeedback(feedback, merkleTreeRoot, nullifierHash, proof)
const transaction = await contract.sendFeedback(merkleTreeDepth, merkleTreeRoot, nullifier, feedback, proof)
await transaction.wait()

View File

@@ -1,4 +1,4 @@
import { Contract, providers, Wallet } from "ethers"
import { Contract, InfuraProvider, JsonRpcProvider, Wallet } from "ethers"
import type { NextApiRequest, NextApiResponse } from "next"
import Feedback from "../../../contract-artifacts/Feedback.json"
@@ -25,9 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const contractAddress = process.env.FEEDBACK_CONTRACT_ADDRESS
const provider =
ethereumNetwork === "localhost"
? new providers.JsonRpcProvider()
: new providers.InfuraProvider(ethereumNetwork, infuraApiKey)
ethereumNetwork === "localhost" ? new JsonRpcProvider() : new InfuraProvider(ethereumNetwork, infuraApiKey)
const signer = new Wallet(ethereumPrivateKey, provider)
const contract = new Contract(contractAddress, Feedback.abi, signer)

View File

@@ -17,14 +17,14 @@ export default function GroupsPage() {
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const identityString = localStorage.getItem("identity")
const privateKey = localStorage.getItem("identity")
if (!identityString) {
if (!privateKey) {
router.push("/")
return
}
setIdentity(new Identity(identityString))
setIdentity(new Identity(privateKey))
}, [])
useEffect(() => {

View File

@@ -10,10 +10,10 @@ export default function IdentitiesPage() {
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const identityString = localStorage.getItem("identity")
const privateKey = localStorage.getItem("identity")
if (identityString) {
const identity = new Identity(identityString)
if (privateKey) {
const identity = new Identity(privateKey)
setIdentity(identity)
@@ -28,7 +28,7 @@ export default function IdentitiesPage() {
setIdentity(identity)
localStorage.setItem("identity", identity.toString())
localStorage.setItem("identity", identity.privateKey.toString())
setLogs("Your new Semaphore identity was just created 🎉")
}, [])
@@ -68,8 +68,7 @@ export default function IdentitiesPage() {
{_identity ? (
<div>
<div className="box">
<p className="box-text">Trapdoor: {_identity.trapdoor.toString()}</p>
<p className="box-text">Nullifier: {_identity.nullifier.toString()}</p>
<p className="box-text">Private Key: {_identity.privateKey.toString()}</p>
<p className="box-text">Commitment: {_identity.commitment.toString()}</p>
</div>
</div>

View File

@@ -1,7 +1,6 @@
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { generateProof } from "@semaphore-protocol/proof"
import { BigNumber, utils } from "ethers"
import getNextConfig from "next/config"
import { useRouter } from "next/router"
import { useCallback, useContext, useEffect, useState } from "react"
@@ -9,6 +8,7 @@ import Feedback from "../../contract-artifacts/Feedback.json"
import Stepper from "../components/Stepper"
import LogsContext from "../context/LogsContext"
import SemaphoreContext from "../context/SemaphoreContext"
import { encodeBytes32String } from "ethers"
const { publicRuntimeConfig: env } = getNextConfig()
@@ -20,14 +20,14 @@ export default function ProofsPage() {
const [_identity, setIdentity] = useState<Identity>()
useEffect(() => {
const identityString = localStorage.getItem("identity")
const privateKey = localStorage.getItem("identity")
if (!identityString) {
if (!privateKey) {
router.push("/")
return
}
setIdentity(new Identity(identityString))
setIdentity(new Identity(privateKey))
}, [])
useEffect(() => {
@@ -51,15 +51,15 @@ export default function ProofsPage() {
try {
const group = new Group(env.GROUP_ID)
const signal = BigNumber.from(utils.formatBytes32String(feedback)).toString()
const message = encodeBytes32String(feedback)
group.addMembers(_users)
const { proof, merkleTreeRoot, nullifierHash } = await generateProof(
const { proof, merkleTreeDepth, merkleTreeRoot, nullifier } = await generateProof(
_identity,
group,
env.GROUP_ID,
signal
message,
env.GROUP_ID
)
let response: any
@@ -72,7 +72,7 @@ export default function ProofsPage() {
abi: Feedback.abi,
address: env.FEEDBACK_CONTRACT_ADDRESS,
functionName: "sendFeedback",
functionParameters: [signal, merkleTreeRoot, nullifierHash, proof]
functionParameters: [merkleTreeDepth, merkleTreeRoot, nullifier, message, proof]
})
})
} else {
@@ -80,9 +80,10 @@ export default function ProofsPage() {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
feedback: signal,
feedback: message,
merkleTreeDepth,
merkleTreeRoot,
nullifierHash,
nullifier,
proof
})
})

View File

@@ -1,6 +1,6 @@
{
"name": "@semaphore-protocol/cli-template-monorepo-subgraph",
"version": "3.15.2",
"version": "4.0.0-alpha",
"description": "Semaphore Hardhat + Next.js + SemaphoreSubgraph template.",
"license": "Unlicense",
"files": [
@@ -22,14 +22,13 @@
"lint": "eslint . --ext .js,.ts",
"prettier": "prettier -c .",
"prettier:write": "prettier -w .",
"copy:contract-artifacts": "ts-node scripts/copy-contract-artifacts.ts",
"prepublish": "tar -czf files.tgz .gitignore .yarn .yarnrc.yml apps"
},
"workspaces": [
"apps/*"
],
"devDependencies": {
"@types/node": "^17.0.9",
"@types/node": "^20",
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"eslint": "^8.2.0",
@@ -40,5 +39,6 @@
"prettier": "^2.5.1",
"ts-node": "^10.8.1",
"typescript": "^4.7.3"
}
},
"stableVersion": "3.15.2"
}

View File

@@ -1,15 +0,0 @@
import * as fs from "fs"
async function main() {
const contractArtifactsPath = "apps/contracts/build/contracts/contracts/Feedback.sol"
const webAppArtifactsPath = "apps/web-app/contract-artifacts"
await fs.promises.copyFile(`${contractArtifactsPath}/Feedback.json`, `${webAppArtifactsPath}/Feedback.json`)
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Ethereum Foundation
Copyright (c) 2024 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

View File

@@ -76,9 +76,9 @@ Options:
Commands:
create [options] [project-directory] Create a Semaphore project with a supported template.
get-groups [options] Get the list of groups from a supported network (e.g. goerli or arbitrum).
get-group [options] [group-id] Get the data of a group from a supported network (e.g. goerli or arbitrum).
get-members [options] [group-id] Get the members of a group from a supported network (e.g. goerli or arbitrum).
get-proofs [options] [group-id] Get the proofs of a group from a supported network (e.g. goerli or arbitrum).
get-groups [options] Get the list of groups from a supported network (e.g. sepolia or arbitrum).
get-group [options] [group-id] Get the data of a group from a supported network (e.g. sepolia or arbitrum).
get-members [options] [group-id] Get the members of a group from a supported network (e.g. sepolia or arbitrum).
get-proofs [options] [group-id] Get the proofs of a group from a supported network (e.g. sepolia or arbitrum).
help [command] Display help for a specific command.
```

View File

@@ -1,7 +1,7 @@
{
"name": "@semaphore-protocol/cli",
"type": "module",
"version": "3.15.2",
"version": "4.0.0-alpha",
"description": "A command line tool to set up your Semaphore project and get group data.",
"license": "MIT",
"bin": {
@@ -23,7 +23,7 @@
"node": ">=14.16"
},
"scripts": {
"start": "ts-node --esm src/index.ts",
"start": "node --loader ts-node/esm --no-warnings src/index.ts",
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
"prepublishOnly": "yarn build"
@@ -42,7 +42,7 @@
"ts-node": "^10.9.1"
},
"dependencies": {
"@semaphore-protocol/data": "3.15.2",
"@semaphore-protocol/data": "4.0.0-alpha",
"axios": "^1.3.2",
"boxen": "^7.0.1",
"chalk": "^5.1.2",
@@ -54,5 +54,6 @@
"ora": "^6.1.2",
"pacote": "^15.1.1",
"semver": "^7.3.8"
}
},
"stableVersion": "3.15.2"
}

View File

@@ -9,7 +9,7 @@ const banner = `#!/usr/bin/env node
* @module ${pkg.name}
* @version ${pkg.version}
* @file ${pkg.description}
* @copyright Ethereum Foundation 2022
* @copyright Ethereum Foundation 2024
* @license ${pkg.license}
* @see [Github]{@link ${pkg.homepage}}
*/

View File

@@ -1,11 +1,11 @@
import { GroupResponse, SemaphoreEthers, SemaphoreSubgraph, getSupportedNetworks } from "@semaphore-protocol/data"
import chalk from "chalk"
import { program } from "commander"
import decompress from "decompress"
import figlet from "figlet"
import { existsSync, readFileSync, unlinkSync, copyFileSync } from "fs"
import { copyFileSync, existsSync, readFileSync, unlinkSync } from "fs"
import logSymbols from "log-symbols"
import pacote from "pacote"
import decompress from "decompress"
import { dirname } from "path"
import { fileURLToPath } from "url"
import checkLatestVersion from "./checkLatestVersion.js"
@@ -117,7 +117,7 @@ program
program
.command("get-groups")
.description("Get the list of groups from a supported network (e.g. goerli or arbitrum).")
.description("Get the list of groups from a supported network (e.g. sepolia or arbitrum).")
.option("-n, --network <network-name>", "Supported Ethereum network.")
.allowExcessArguments(false)
.action(async ({ network }) => {
@@ -146,7 +146,7 @@ program
program
.command("get-group")
.description("Get the data of a group from a supported network (e.g. goerli or arbitrum).")
.description("Get the data of a group from a supported network (e.g. sepolia or arbitrum).")
.argument("[group-id]", "Identifier of the group.")
.option("-n, --network <network-name>", "Supported Ethereum network.")
.allowExcessArguments(false)
@@ -209,15 +209,14 @@ program
content += ` ${chalk.bold("Merkle tree")}:\n`
content += ` Root: ${group.merkleTree.root}\n`
content += ` Depth: ${group.merkleTree.depth}\n`
content += ` Zero value: ${group.merkleTree.zeroValue}\n`
content += ` Number of leaves: ${group.merkleTree.numberOfLeaves}`
content += ` Size: ${group.merkleTree.size}`
console.info(`\n${content}\n`)
})
program
.command("get-members")
.description("Get the members of a group from a supported network (e.g. goerli or arbitrum).")
.description("Get the members of a group from a supported network (e.g. sepolia or arbitrum).")
.argument("[group-id]", "Identifier of the group.")
.option("-n, --network <network-name>", "Supported Ethereum network.")
.allowExcessArguments(false)
@@ -283,7 +282,7 @@ program
program
.command("get-proofs")
.description("Get the proofs of a group from a supported network (e.g. goerli or arbitrum).")
.description("Get the proofs of a group from a supported network (e.g. sepolia or arbitrum).")
.argument("[group-id]", "Identifier of the group.")
.option("-n, --network <network-name>", "Supported Ethereum network.")
.allowExcessArguments(false)
@@ -308,7 +307,7 @@ program
groupId = await getGroupId(groupIds)
}
let verifiedProofs: any[]
let validatedProofs: any[]
const spinner = new Spinner(`Fetching proofs of group ${groupId}`)
@@ -317,15 +316,15 @@ program
try {
const semaphoreSubgraph = new SemaphoreSubgraph(network)
const group = await semaphoreSubgraph.getGroup(groupId, { verifiedProofs: true })
verifiedProofs = group.verifiedProofs
const group = await semaphoreSubgraph.getGroup(groupId, { validatedProofs: true })
validatedProofs = group.validatedProofs
spinner.stop()
} catch {
try {
const semaphoreEthers = new SemaphoreEthers(network)
verifiedProofs = await semaphoreEthers.getGroupVerifiedProofs(groupId)
validatedProofs = await semaphoreEthers.getGroupValidatedProofs(groupId)
spinner.stop()
} catch {
@@ -335,15 +334,17 @@ program
}
}
if (verifiedProofs.length === 0) {
if (validatedProofs.length === 0) {
console.info(`\n ${logSymbols.info}`, "info: there are no proofs in this group\n")
return
}
const content = `${chalk.bold("Proofs")} (${verifiedProofs.length}): \n${verifiedProofs
const content = `${chalk.bold("Proofs")} (${validatedProofs.length}): \n${validatedProofs
.map(
({ signal, merkleTreeRoot, externalNullifier, nullifierHash }: any, i: number) =>
` ${i}. signal: ${signal} \n merkleTreeRoot: ${merkleTreeRoot} \n externalNullifier: ${externalNullifier} \n nullifierHash: ${nullifierHash}`
({ message, merkleTreeRoot, merkleTreeDepth, scope, nullifier, proof }: any, i: number) =>
` ${i}. message: ${message} \n merkleTreeRoot: ${merkleTreeRoot} \n merkleTreeDepth: ${merkleTreeDepth} \n scope: ${scope} \n nullifier: ${nullifier} \n proof: [${proof.join(
", "
)}]`
)
.join("\n")}`

View File

@@ -1 +0,0 @@
contracts/base/Pairing.sol

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Ethereum Foundation
Copyright (c) 2024 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

View File

@@ -107,14 +107,14 @@ yarn deploy:semaphore --semaphoreVerifier <address>
> **Note**
> Run `yarn deploy:semaphore --help` to see the complete list.
If you want to deploy your contract in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as an option:
If you want to deploy your contract in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, sepolia, arbitrum). Or you can specify it as an option:
```bash
yarn deploy:semaphore --network goerli
yarn deploy:semaphore --network sepolia
yarn deploy:semaphore --network mumbai
yarn deploy:semaphore --network optimism-goerli
yarn deploy:semaphore --network optimism-sepolia
yarn deploy:semaphore --network arbitrum-sepolia
yarn deploy:semaphore --network arbitrum
```
If you want to deploy contracts on Goerli or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file.
If you want to deploy contracts on Sepolia or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file.

View File

@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
pragma solidity 0.8.23;
import "./interfaces/ISemaphore.sol";
import "./interfaces/ISemaphoreVerifier.sol";
import "./base/SemaphoreGroups.sol";
import {ISemaphore} from "./interfaces/ISemaphore.sol";
import {ISemaphoreVerifier} from "./interfaces/ISemaphoreVerifier.sol";
import {SemaphoreGroups} from "./base/SemaphoreGroups.sol";
/// @title Semaphore
/// @dev This contract uses the Semaphore base contracts to provide a complete service
@@ -18,71 +18,36 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
/// @dev Gets a group id and returns the group parameters.
mapping(uint256 => Group) public groups;
/// @dev Checks if the group admin is the transaction sender.
/// @param groupId: Id of the group.
modifier onlyGroupAdmin(uint256 groupId) {
if (groups[groupId].admin != _msgSender()) {
revert Semaphore__CallerIsNotTheGroupAdmin();
}
_;
}
/// @dev Checks if there is a verifier for the given tree depth.
/// @param merkleTreeDepth: Depth of the tree.
modifier onlySupportedMerkleTreeDepth(uint256 merkleTreeDepth) {
if (merkleTreeDepth < 16 || merkleTreeDepth > 32) {
revert Semaphore__MerkleTreeDepthIsNotSupported();
}
_;
}
/// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs.
/// @param _verifier: Semaphore verifier address.
/// @param _verifier: Semaphore verifier addresse.
constructor(ISemaphoreVerifier _verifier) {
verifier = _verifier;
}
/// @dev See {ISemaphore-createGroup}.
function createGroup(
uint256 groupId,
uint256 merkleTreeDepth,
address admin
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
_createGroup(groupId, merkleTreeDepth);
/// @dev See {SemaphoreGroups-_createGroup}.
function createGroup(uint256 groupId, address admin) external override {
_createGroup(groupId, admin);
groups[groupId].admin = admin;
groups[groupId].merkleTreeDuration = 1 hours;
emit GroupAdminUpdated(groupId, address(0), admin);
}
/// @dev See {ISemaphore-createGroup}.
function createGroup(
uint256 groupId,
uint256 merkleTreeDepth,
address admin,
uint256 merkleTreeDuration
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
_createGroup(groupId, merkleTreeDepth);
function createGroup(uint256 groupId, address admin, uint256 merkleTreeDuration) external override {
_createGroup(groupId, admin);
groups[groupId].admin = admin;
groups[groupId].merkleTreeDuration = merkleTreeDuration;
emit GroupAdminUpdated(groupId, address(0), admin);
}
/// @dev See {ISemaphore-updateGroupAdmin}.
function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) {
groups[groupId].admin = newAdmin;
emit GroupAdminUpdated(groupId, _msgSender(), newAdmin);
/// @dev See {SemaphoreGroups-_updateGroupAdmin}.
function updateGroupAdmin(uint256 groupId, address newAdmin) external override {
_updateGroupAdmin(groupId, newAdmin);
}
/// @dev See {ISemaphore-updateGroupMerkleTreeDuration}.
function updateGroupMerkleTreeDuration(
uint256 groupId,
uint256 newMerkleTreeDuration
) external override onlyGroupAdmin(groupId) {
) external override onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
uint256 oldMerkleTreeDuration = groups[groupId].merkleTreeDuration;
groups[groupId].merkleTreeDuration = newMerkleTreeDuration;
@@ -90,8 +55,8 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
emit GroupMerkleTreeDurationUpdated(groupId, oldMerkleTreeDuration, newMerkleTreeDuration);
}
/// @dev See {ISemaphore-addMember}.
function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) {
/// @dev See {SemaphoreGroups-_addMember}.
function addMember(uint256 groupId, uint256 identityCommitment) external override {
_addMember(groupId, identityCommitment);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
@@ -99,66 +64,81 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}
/// @dev See {ISemaphore-addMembers}.
function addMembers(
uint256 groupId,
uint256[] calldata identityCommitments
) external override onlyGroupAdmin(groupId) {
for (uint256 i = 0; i < identityCommitments.length; ) {
_addMember(groupId, identityCommitments[i]);
unchecked {
++i;
}
}
/// @dev See {SemaphoreGroups-_addMembers}.
function addMembers(uint256 groupId, uint256[] calldata identityCommitments) external override {
_addMembers(groupId, identityCommitments);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}
/// @dev See {ISemaphore-updateMember}.
/// @dev See {SemaphoreGroups-_updateMember}.
function updateMember(
uint256 groupId,
uint256 identityCommitment,
uint256 newIdentityCommitment,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) external override onlyGroupAdmin(groupId) {
_updateMember(groupId, identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
uint256[] calldata merkleProofSiblings
) external override {
_updateMember(groupId, identityCommitment, newIdentityCommitment, merkleProofSiblings);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}
/// @dev See {ISemaphore-removeMember}.
/// @dev See {SemaphoreGroups-_removeMember}.
function removeMember(
uint256 groupId,
uint256 identityCommitment,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) external override onlyGroupAdmin(groupId) {
_removeMember(groupId, identityCommitment, proofSiblings, proofPathIndices);
uint256[] calldata merkleProofSiblings
) external override {
_removeMember(groupId, identityCommitment, merkleProofSiblings);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
groups[groupId].merkleRootCreationDates[merkleTreeRoot] = block.timestamp;
}
/// @dev See {ISemaphore-verifyProof}.
function validateProof(
uint256 groupId,
uint256 merkleTreeDepth,
uint256 merkleTreeRoot,
uint256 nullifier,
uint256 message,
uint256 scope,
uint256[8] calldata proof
) external override onlyExistingGroup(groupId) {
if (groups[groupId].nullifiers[nullifier]) {
revert Semaphore__YouAreUsingTheSameNullifierTwice();
}
if (!verifyProof(groupId, merkleTreeDepth, merkleTreeRoot, nullifier, message, scope, proof)) {
revert Semaphore__InvalidProof();
}
groups[groupId].nullifiers[nullifier] = true;
emit ProofValidated(groupId, merkleTreeDepth, merkleTreeRoot, nullifier, message, scope, proof);
}
function verifyProof(
uint256 groupId,
uint256 merkleTreeDepth,
uint256 merkleTreeRoot,
uint256 signal,
uint256 nullifierHash,
uint256 externalNullifier,
uint256 nullifier,
uint256 message,
uint256 scope,
uint256[8] calldata proof
) external override {
uint256 merkleTreeDepth = getMerkleTreeDepth(groupId);
) public view override onlyExistingGroup(groupId) returns (bool) {
if (merkleTreeDepth < 1 || merkleTreeDepth > 12) {
revert Semaphore__MerkleTreeDepthIsNotSupported();
}
if (merkleTreeDepth == 0) {
revert Semaphore__GroupDoesNotExist();
uint256 merkleTreeSize = getMerkleTreeSize(groupId);
if (merkleTreeSize == 0) {
revert Semaphore__GroupHasNoMembers();
}
uint256 currentMerkleTreeRoot = getMerkleTreeRoot(groupId);
@@ -178,14 +158,20 @@ contract Semaphore is ISemaphore, SemaphoreGroups {
}
}
if (groups[groupId].nullifierHashes[nullifierHash]) {
revert Semaphore__YouAreUsingTheSameNillifierTwice();
}
return
verifier.verifyProof(
[proof[0], proof[1]],
[[proof[2], proof[3]], [proof[4], proof[5]]],
[proof[6], proof[7]],
[merkleTreeRoot, nullifier, _hash(message), _hash(scope)],
merkleTreeDepth
);
}
verifier.verifyProof(merkleTreeRoot, nullifierHash, signal, externalNullifier, proof, merkleTreeDepth);
groups[groupId].nullifierHashes[nullifierHash] = true;
emit ProofVerified(groupId, merkleTreeRoot, nullifierHash, externalNullifier, signal);
/// @dev Creates a keccak256 hash of a message compatible with the SNARK scalar modulus.
/// @param message: Message to be hashed.
/// @return Message digest.
function _hash(uint256 message) private pure returns (uint256) {
return uint256(keccak256(abi.encodePacked(message))) >> 8;
}
}

View File

@@ -1,151 +0,0 @@
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// The following Pairing library is a modified version adapted to Semaphore.
//
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
library Pairing {
error InvalidProof();
// The prime q in the base field F_q for G1
uint256 constant BASE_MODULUS = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
// The prime modulus of the scalar field of G1.
uint256 constant SCALAR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
struct G1Point {
uint256 X;
uint256 Y;
}
// Encoding of field elements is: X[0] * z + X[1]
struct G2Point {
uint256[2] X;
uint256[2] Y;
}
/// @return the generator of G1
function P1() public pure returns (G1Point memory) {
return G1Point(1, 2);
}
/// @return the generator of G2
function P2() public pure returns (G2Point memory) {
return
G2Point(
[
11559732032986387107991004021392285783925812861821192530917403151452391805634,
10857046999023057135944570762232829481370756359578518086990519993285655852781
],
[
4082367875863433681332203403145435568316851327593401208105741076214120093531,
8495653923123431417604973247489272438418190587263600148770280649306958101930
]
);
}
/// @return r the negation of p, i.e. p.addition(p.negate()) should be zero.
function negate(G1Point memory p) public pure returns (G1Point memory r) {
if (p.X == 0 && p.Y == 0) {
return G1Point(0, 0);
}
// Validate input or revert
if (p.X >= BASE_MODULUS || p.Y >= BASE_MODULUS) {
revert InvalidProof();
}
// We know p.Y > 0 and p.Y < BASE_MODULUS.
return G1Point(p.X, BASE_MODULUS - p.Y);
}
/// @return r the sum of two points of G1
function addition(G1Point memory p1, G1Point memory p2) public view returns (G1Point memory r) {
// By EIP-196 all input is validated to be less than the BASE_MODULUS and form points
// on the curve.
uint256[4] memory input;
input[0] = p1.X;
input[1] = p1.Y;
input[2] = p2.X;
input[3] = p2.Y;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
}
if (!success) {
revert InvalidProof();
}
}
/// @return r the product of a point on G1 and a scalar, i.e.
/// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
function scalar_mul(G1Point memory p, uint256 s) public view returns (G1Point memory r) {
// By EIP-196 the values p.X and p.Y are verified to be less than the BASE_MODULUS and
// form a valid point on the curve. But the scalar is not verified, so we do that explicitly.
if (s >= SCALAR_MODULUS) {
revert InvalidProof();
}
uint256[3] memory input;
input[0] = p.X;
input[1] = p.Y;
input[2] = s;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
}
if (!success) {
revert InvalidProof();
}
}
/// Asserts the pairing check
/// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
/// For example pairing([P1(), P1().negate()], [P2(), P2()]) should succeed
function pairingCheck(G1Point[] memory p1, G2Point[] memory p2) public view {
// By EIP-197 all input is verified to be less than the BASE_MODULUS and form elements in their
// respective groups of the right order.
if (p1.length != p2.length) {
revert InvalidProof();
}
uint256 elements = p1.length;
uint256 inputSize = elements * 6;
uint256[] memory input = new uint256[](inputSize);
for (uint256 i = 0; i < elements; i++) {
input[i * 6 + 0] = p1[i].X;
input[i * 6 + 1] = p1[i].Y;
input[i * 6 + 2] = p2[i].X[0];
input[i * 6 + 3] = p2[i].X[1];
input[i * 6 + 4] = p2[i].Y[0];
input[i * 6 + 5] = p2[i].Y[1];
}
uint256[1] memory out;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
}
if (!success || out[0] != 1) {
revert InvalidProof();
}
}
}

View File

@@ -1,106 +1,141 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
pragma solidity 0.8.23;
import "../interfaces/ISemaphoreGroups.sol";
import "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import {ISemaphoreGroups} from "../interfaces/ISemaphoreGroups.sol";
import {InternalLeanIMT, LeanIMTData} from "@zk-kit/imt.sol/internal/InternalLeanIMT.sol";
/// @title Semaphore groups contract.
/// @dev This contract allows you to create groups, add, remove and update members.
/// You can use getters to obtain informations about groups (root, depth, number of leaves).
abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
using IncrementalBinaryTree for IncrementalTreeData;
abstract contract SemaphoreGroups is ISemaphoreGroups {
using InternalLeanIMT for LeanIMTData;
/// @dev Gets a group id and returns the tree data.
mapping(uint256 => IncrementalTreeData) internal merkleTrees;
/// @dev Gets a group id and returns its tree data.
mapping(uint256 => LeanIMTData) internal merkleTrees;
/// @dev Creates a new group by initializing the associated tree.
/// @dev Gets a group id and returns its admin.
mapping(uint256 => address) internal admins;
/// @dev Checks if the group admin is the transaction sender.
/// @param groupId: Id of the group.
/// @param merkleTreeDepth: Depth of the tree.
function _createGroup(uint256 groupId, uint256 merkleTreeDepth) internal virtual {
if (getMerkleTreeDepth(groupId) != 0) {
modifier onlyGroupAdmin(uint256 groupId) {
if (admins[groupId] != msg.sender) {
revert Semaphore__CallerIsNotTheGroupAdmin();
}
_;
}
/// @dev Checks if the group exists.
/// @param groupId: Id of the group.
modifier onlyExistingGroup(uint256 groupId) {
if (admins[groupId] == address(0)) {
revert Semaphore__GroupDoesNotExist();
}
_;
}
/// @dev Creates a new group. Only the admin will be able to add or remove members.
/// @param groupId: Id of the group.
/// @param admin: Admin of the group.
function _createGroup(uint256 groupId, address admin) internal virtual {
if (admins[groupId] != address(0)) {
revert Semaphore__GroupAlreadyExists();
}
// The zeroValue is an implicit member of the group, or an implicit leaf of the Merkle tree.
// Although there is a remote possibility that the preimage of
// the hash may be calculated, using this value we aim to minimize the risk.
uint256 zeroValue = uint256(keccak256(abi.encodePacked(groupId))) >> 8;
admins[groupId] = admin;
merkleTrees[groupId].init(merkleTreeDepth, zeroValue);
emit GroupCreated(groupId);
emit GroupAdminUpdated(groupId, address(0), admin);
}
emit GroupCreated(groupId, merkleTreeDepth, zeroValue);
/// @dev Updates the group admin.
/// @param groupId: Id of the group.
/// @param newAdmin: New admin of the group.
function _updateGroupAdmin(
uint256 groupId,
address newAdmin
) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
admins[groupId] = newAdmin;
emit GroupAdminUpdated(groupId, msg.sender, newAdmin);
}
/// @dev Adds an identity commitment to an existing group.
/// @param groupId: Id of the group.
/// @param identityCommitment: New identity commitment.
function _addMember(uint256 groupId, uint256 identityCommitment) internal virtual {
if (getMerkleTreeDepth(groupId) == 0) {
revert Semaphore__GroupDoesNotExist();
}
merkleTrees[groupId].insert(identityCommitment);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
uint256 index = getNumberOfMerkleTreeLeaves(groupId) - 1;
function _addMember(
uint256 groupId,
uint256 identityCommitment
) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
uint256 index = getMerkleTreeSize(groupId);
uint256 merkleTreeRoot = merkleTrees[groupId]._insert(identityCommitment);
emit MemberAdded(groupId, index, identityCommitment, merkleTreeRoot);
}
/// @dev Adds new members to an existing group.
/// @param groupId: Id of the group.
/// @param identityCommitments: New identity commitments.
function _addMembers(uint256 groupId, uint256[] calldata identityCommitments) internal virtual {
uint256 startIndex = getMerkleTreeSize(groupId);
uint256 merkleTreeRoot = merkleTrees[groupId]._insertMany(identityCommitments);
emit MembersAdded(groupId, startIndex, identityCommitments, merkleTreeRoot);
}
/// @dev Updates an identity commitment of an existing group. A proof of membership is
/// needed to check if the node to be updated is part of the tree.
/// @param groupId: Id of the group.
/// @param identityCommitment: Existing identity commitment to be updated.
/// @param oldIdentityCommitment: Existing identity commitment to be updated.
/// @param newIdentityCommitment: New identity commitment.
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
/// @param proofPathIndices: Path of the proof of membership.
/// @param merkleProofSiblings: Array of the sibling nodes of the proof of membership.
function _updateMember(
uint256 groupId,
uint256 identityCommitment,
uint256 oldIdentityCommitment,
uint256 newIdentityCommitment,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) internal virtual {
if (getMerkleTreeDepth(groupId) == 0) {
revert Semaphore__GroupDoesNotExist();
}
uint256[] calldata merkleProofSiblings
) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
uint256 index = merkleTrees[groupId]._indexOf(oldIdentityCommitment);
uint256 merkleTreeRoot = merkleTrees[groupId]._update(
oldIdentityCommitment,
newIdentityCommitment,
merkleProofSiblings
);
merkleTrees[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
emit MemberUpdated(groupId, index, identityCommitment, newIdentityCommitment, merkleTreeRoot);
emit MemberUpdated(groupId, index, oldIdentityCommitment, newIdentityCommitment, merkleTreeRoot);
}
/// @dev Removes an identity commitment from an existing group. A proof of membership is
/// needed to check if the node to be deleted is part of the tree.
/// @param groupId: Id of the group.
/// @param identityCommitment: Existing identity commitment to be removed.
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
/// @param proofPathIndices: Path of the proof of membership.
/// @param merkleProofSiblings: Array of the sibling nodes of the proof of membership.
function _removeMember(
uint256 groupId,
uint256 identityCommitment,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) internal virtual {
if (getMerkleTreeDepth(groupId) == 0) {
revert Semaphore__GroupDoesNotExist();
}
uint256[] calldata merkleProofSiblings
) internal virtual onlyExistingGroup(groupId) onlyGroupAdmin(groupId) {
uint256 index = merkleTrees[groupId]._indexOf(identityCommitment);
merkleTrees[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
uint256 merkleTreeRoot = merkleTrees[groupId]._remove(identityCommitment, merkleProofSiblings);
emit MemberRemoved(groupId, index, identityCommitment, merkleTreeRoot);
}
/// @dev See {ISemaphoreGroups-hasMember}.
function hasMember(uint256 groupId, uint256 identityCommitment) public view virtual override returns (bool) {
return merkleTrees[groupId]._has(identityCommitment);
}
/// @dev See {ISemaphoreGroups-indexOf}.
function indexOf(uint256 groupId, uint256 identityCommitment) public view virtual override returns (uint256) {
return merkleTrees[groupId]._indexOf(identityCommitment);
}
/// @dev See {ISemaphoreGroups-getMerkleTreeRoot}.
function getMerkleTreeRoot(uint256 groupId) public view virtual override returns (uint256) {
return merkleTrees[groupId].root;
return merkleTrees[groupId]._root();
}
/// @dev See {ISemaphoreGroups-getMerkleTreeDepth}.
@@ -108,31 +143,8 @@ abstract contract SemaphoreGroups is Context, ISemaphoreGroups {
return merkleTrees[groupId].depth;
}
/// @dev See {ISemaphoreGroups-getNumberOfMerkleTreeLeaves}.
function getNumberOfMerkleTreeLeaves(uint256 groupId) public view virtual override returns (uint256) {
return merkleTrees[groupId].numberOfLeaves;
}
/// @dev Converts the path indices of a Merkle proof to the identity commitment index in the tree.
/// @param proofPathIndices: Path of the proof of membership.
/// @return Index of a group member.
function proofPathIndicesToMemberIndex(uint8[] calldata proofPathIndices) private pure returns (uint256) {
uint256 memberIndex = 0;
for (uint8 i = uint8(proofPathIndices.length); i > 0; ) {
if (memberIndex > 0 || proofPathIndices[i - 1] != 0) {
memberIndex *= 2;
if (proofPathIndices[i - 1] == 1) {
memberIndex += 1;
}
}
unchecked {
--i;
}
}
return memberIndex;
/// @dev See {ISemaphoreGroups-getMerkleTreeSize}.
function getMerkleTreeSize(uint256 groupId) public view virtual override returns (uint256) {
return merkleTrees[groupId].size;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,96 +0,0 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import "../interfaces/ISemaphoreVoting.sol";
import "../interfaces/ISemaphoreVerifier.sol";
import "../base/SemaphoreGroups.sol";
/// @title Semaphore voting contract.
/// @notice It allows users to vote anonymously in a poll.
/// @dev The following code allows you to create polls, add voters and allow them to vote anonymously.
contract SemaphoreVoting is ISemaphoreVoting, SemaphoreGroups {
ISemaphoreVerifier public verifier;
/// @dev Gets a poll id and returns the poll data.
mapping(uint256 => Poll) internal polls;
/// @dev Checks if the poll coordinator is the transaction sender.
/// @param pollId: Id of the poll.
modifier onlyCoordinator(uint256 pollId) {
if (polls[pollId].coordinator != _msgSender()) {
revert Semaphore__CallerIsNotThePollCoordinator();
}
_;
}
/// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs.
/// @param _verifier: Semaphore verifier address.
constructor(ISemaphoreVerifier _verifier) {
verifier = _verifier;
}
/// @dev See {ISemaphoreVoting-createPoll}.
function createPoll(uint256 pollId, address coordinator, uint256 merkleTreeDepth) public override {
if (merkleTreeDepth < 16 || merkleTreeDepth > 32) {
revert Semaphore__MerkleTreeDepthIsNotSupported();
}
_createGroup(pollId, merkleTreeDepth);
polls[pollId].coordinator = coordinator;
emit PollCreated(pollId, coordinator);
}
/// @dev See {ISemaphoreVoting-addVoter}.
function addVoter(uint256 pollId, uint256 identityCommitment) public override onlyCoordinator(pollId) {
if (polls[pollId].state != PollState.Created) {
revert Semaphore__PollHasAlreadyBeenStarted();
}
_addMember(pollId, identityCommitment);
}
/// @dev See {ISemaphoreVoting-addVoter}.
function startPoll(uint256 pollId, uint256 encryptionKey) public override onlyCoordinator(pollId) {
if (polls[pollId].state != PollState.Created) {
revert Semaphore__PollHasAlreadyBeenStarted();
}
polls[pollId].state = PollState.Ongoing;
emit PollStarted(pollId, _msgSender(), encryptionKey);
}
/// @dev See {ISemaphoreVoting-castVote}.
function castVote(uint256 vote, uint256 nullifierHash, uint256 pollId, uint256[8] calldata proof) public override {
if (polls[pollId].state != PollState.Ongoing) {
revert Semaphore__PollIsNotOngoing();
}
if (polls[pollId].nullifierHashes[nullifierHash]) {
revert Semaphore__YouAreUsingTheSameNillifierTwice();
}
uint256 merkleTreeDepth = getMerkleTreeDepth(pollId);
uint256 merkleTreeRoot = getMerkleTreeRoot(pollId);
verifier.verifyProof(merkleTreeRoot, nullifierHash, vote, pollId, proof, merkleTreeDepth);
polls[pollId].nullifierHashes[nullifierHash] = true;
emit VoteAdded(pollId, vote);
}
/// @dev See {ISemaphoreVoting-publishDecryptionKey}.
function endPoll(uint256 pollId, uint256 decryptionKey) public override onlyCoordinator(pollId) {
if (polls[pollId].state != PollState.Ongoing) {
revert Semaphore__PollIsNotOngoing();
}
polls[pollId].state = PollState.Ended;
emit PollEnded(pollId, _msgSender(), decryptionKey);
}
}

View File

@@ -1,77 +0,0 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
import "../interfaces/ISemaphoreWhistleblowing.sol";
import "../interfaces/ISemaphoreVerifier.sol";
import "../base/SemaphoreGroups.sol";
/// @title Semaphore whistleblowing contract.
/// @notice It allows users to leak information anonymously .
/// @dev The following code allows you to create entities for whistleblowers (e.g. non-profit
/// organization, newspaper) and allow them to leak anonymously.
/// Leaks can be IPFS hashes, permanent links or other kinds of references.
contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreGroups {
ISemaphoreVerifier public verifier;
/// @dev Gets an entity id and return its editor address.
mapping(uint256 => address) private entities;
/// @dev Checks if the editor is the transaction sender.
/// @param entityId: Id of the entity.
modifier onlyEditor(uint256 entityId) {
if (entities[entityId] != _msgSender()) {
revert Semaphore__CallerIsNotTheEditor();
}
_;
}
/// @dev Initializes the Semaphore verifier used to verify the user's ZK proofs.
/// @param _verifier: Semaphore verifier address.
constructor(ISemaphoreVerifier _verifier) {
verifier = _verifier;
}
/// @dev See {ISemaphoreWhistleblowing-createEntity}.
function createEntity(uint256 entityId, address editor, uint256 merkleTreeDepth) public override {
if (merkleTreeDepth < 16 || merkleTreeDepth > 32) {
revert Semaphore__MerkleTreeDepthIsNotSupported();
}
_createGroup(entityId, merkleTreeDepth);
entities[entityId] = editor;
emit EntityCreated(entityId, editor);
}
/// @dev See {ISemaphoreWhistleblowing-addWhistleblower}.
function addWhistleblower(uint256 entityId, uint256 identityCommitment) public override onlyEditor(entityId) {
_addMember(entityId, identityCommitment);
}
/// @dev See {ISemaphoreWhistleblowing-removeWhistleblower}.
function removeWhistleblower(
uint256 entityId,
uint256 identityCommitment,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) public override onlyEditor(entityId) {
_removeMember(entityId, identityCommitment, proofSiblings, proofPathIndices);
}
/// @dev See {ISemaphoreWhistleblowing-publishLeak}.
function publishLeak(
uint256 leak,
uint256 nullifierHash,
uint256 entityId,
uint256[8] calldata proof
) public override {
uint256 merkleTreeDepth = getMerkleTreeDepth(entityId);
uint256 merkleTreeRoot = getMerkleTreeRoot(entityId);
verifier.verifyProof(merkleTreeRoot, nullifierHash, leak, entityId, proof, merkleTreeDepth);
emit LeakPublished(entityId, leak);
}
}

View File

@@ -1,28 +1,22 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.4;
pragma solidity 0.8.23;
/// @title Semaphore contract interface.
interface ISemaphore {
error Semaphore__CallerIsNotTheGroupAdmin();
error Semaphore__GroupHasNoMembers();
error Semaphore__MerkleTreeDepthIsNotSupported();
error Semaphore__MerkleTreeRootIsExpired();
error Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
error Semaphore__YouAreUsingTheSameNillifierTwice();
error Semaphore__YouAreUsingTheSameNullifierTwice();
error Semaphore__InvalidProof();
/// It defines all the group parameters, in addition to those in the Merkle tree.
/// It defines all the group parameters used by Semaphore.sol.
struct Group {
address admin;
uint256 merkleTreeDuration;
mapping(uint256 => uint256) merkleRootCreationDates;
mapping(uint256 => bool) nullifierHashes;
mapping(uint256 => bool) nullifiers;
}
/// @dev Emitted when an admin is assigned to a group.
/// @param groupId: Id of the group.
/// @param oldAdmin: Old admin of the group.
/// @param newAdmin: New admin of the group.
event GroupAdminUpdated(uint256 indexed groupId, address indexed oldAdmin, address indexed newAdmin);
/// @dev Emitted when the Merkle tree duration of a group is updated.
/// @param groupId: Id of the group.
/// @param oldMerkleTreeDuration: Old Merkle tree duration of the group.
@@ -33,53 +27,34 @@ interface ISemaphore {
uint256 newMerkleTreeDuration
);
/// @dev Emitted when a Semaphore proof is verified.
/// @dev Emitted when a Semaphore proof is validated.
/// @param groupId: Id of the group.
/// @param merkleTreeDepth: Depth of the Merkle tree.
/// @param merkleTreeRoot: Root of the Merkle tree.
/// @param nullifierHash: Nullifier hash.
/// @param externalNullifier: External nullifier.
/// @param signal: Semaphore signal.
event ProofVerified(
/// @param nullifier: Nullifier.
/// @param message: Semaphore message.
/// @param scope: Scope.
/// @param proof: Zero-knowledge proof.
event ProofValidated(
uint256 indexed groupId,
uint256 merkleTreeDepth,
uint256 indexed merkleTreeRoot,
uint256 nullifierHash,
uint256 indexed externalNullifier,
uint256 signal
uint256 nullifier,
uint256 message,
uint256 indexed scope,
uint256[8] proof
);
/// @dev Saves the nullifier hash to avoid double signaling and emits an event
/// if the zero-knowledge proof is valid.
/// @param groupId: Id of the group.
/// @param merkleTreeRoot: Root of the Merkle tree.
/// @param signal: Semaphore signal.
/// @param nullifierHash: Nullifier hash.
/// @param externalNullifier: External nullifier.
/// @param proof: Zero-knowledge proof.
function verifyProof(
uint256 groupId,
uint256 merkleTreeRoot,
uint256 signal,
uint256 nullifierHash,
uint256 externalNullifier,
uint256[8] calldata proof
) external;
/// @dev See {SemaphoreGroups-_createGroup}.
function createGroup(uint256 groupId, address admin) external;
/// @dev Creates a new group. Only the admin will be able to add or remove members.
/// @dev It creates a group with a custom Merkle tree duration.
/// @param groupId: Id of the group.
/// @param depth: Depth of the tree.
/// @param admin: Admin of the group.
function createGroup(uint256 groupId, uint256 depth, address admin) external;
/// @param merkleTreeDuration: Merkle tree duration.
function createGroup(uint256 groupId, address admin, uint256 merkleTreeDuration) external;
/// @dev Creates a new group. Only the admin will be able to add or remove members.
/// @param groupId: Id of the group.
/// @param depth: Depth of the tree.
/// @param admin: Admin of the group.
/// @param merkleTreeRootDuration: Time before the validity of a root expires.
function createGroup(uint256 groupId, uint256 depth, address admin, uint256 merkleTreeRootDuration) external;
/// @dev Updates the group admin.
/// @param groupId: Id of the group.
/// @param newAdmin: New admin of the group.
/// @dev See {SemaphoreGroups-_updateGroupAdmin}.
function updateGroupAdmin(uint256 groupId, address newAdmin) external;
/// @dev Updates the group Merkle tree duration.
@@ -87,41 +62,57 @@ interface ISemaphore {
/// @param newMerkleTreeDuration: New Merkle tree duration.
function updateGroupMerkleTreeDuration(uint256 groupId, uint256 newMerkleTreeDuration) external;
/// @dev Adds a new member to an existing group.
/// @param groupId: Id of the group.
/// @param identityCommitment: New identity commitment.
/// @dev See {SemaphoreGroups-_addMember}.
function addMember(uint256 groupId, uint256 identityCommitment) external;
/// @dev Adds new members to an existing group.
/// @param groupId: Id of the group.
/// @param identityCommitments: New identity commitments.
/// @dev See {SemaphoreGroups-_addMembers}.
function addMembers(uint256 groupId, uint256[] calldata identityCommitments) external;
/// @dev Updates an identity commitment of an existing group. A proof of membership is
/// needed to check if the node to be updated is part of the tree.
/// @param groupId: Id of the group.
/// @param identityCommitment: Existing identity commitment to be updated.
/// @param newIdentityCommitment: New identity commitment.
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
/// @param proofPathIndices: Path of the proof of membership.
/// @dev See {SemaphoreGroups-_updateMember}.
function updateMember(
uint256 groupId,
uint256 identityCommitment,
uint256 oldIdentityCommitment,
uint256 newIdentityCommitment,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
uint256[] calldata merkleProofSiblings
) external;
/// @dev Removes a member from an existing group. A proof of membership is
/// needed to check if the node to be removed is part of the tree.
/// @dev See {SemaphoreGroups-_removeMember}.
function removeMember(uint256 groupId, uint256 identityCommitment, uint256[] calldata merkleProofSiblings) external;
/// @dev Saves the nullifier hash to avoid double signaling and emits an event
/// if the zero-knowledge proof is valid.
/// @param groupId: Id of the group.
/// @param identityCommitment: Identity commitment to be removed.
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
/// @param proofPathIndices: Path of the proof of membership.
function removeMember(
/// @param merkleTreeDepth: Depth of the Merkle tree.
/// @param merkleTreeRoot: Root of the Merkle tree.
/// @param nullifier: Nullifier.
/// @param message: Semaphore message.
/// @param scope: Scope.
/// @param proof: Zero-knowledge proof.
function validateProof(
uint256 groupId,
uint256 identityCommitment,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
uint256 merkleTreeDepth,
uint256 merkleTreeRoot,
uint256 nullifier,
uint256 message,
uint256 scope,
uint256[8] calldata proof
) external;
/// @dev Verifies a zero-knowledge proof by returning true or false.
/// @param groupId: Id of the group.
/// @param merkleTreeDepth: Depth of the Merkle tree.
/// @param merkleTreeRoot: Root of the Merkle tree.
/// @param nullifier: Nullifier.
/// @param message: Semaphore message.
/// @param scope: Scope.
/// @param proof: Zero-knowledge proof.
function verifyProof(
uint256 groupId,
uint256 merkleTreeDepth,
uint256 merkleTreeRoot,
uint256 nullifier,
uint256 message,
uint256 scope,
uint256[8] calldata proof
) external view returns (bool);
}

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