mirror of
https://github.com/semaphore-protocol/semaphore.git
synced 2026-01-11 23:58:06 -05:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
236e6b241c | ||
|
|
e259482ad8 | ||
|
|
76101a0c54 | ||
|
|
8489d149c7 | ||
|
|
dc0298599b | ||
|
|
55fa4203d2 | ||
|
|
1cd0e5f9d7 | ||
|
|
08961c0197 | ||
|
|
bbe13aa12a | ||
|
|
d5a9ba4b60 | ||
|
|
ba5f2a038f | ||
|
|
375b79628e | ||
|
|
242924fff4 | ||
|
|
18e0afc711 | ||
|
|
ab9a0e84f3 | ||
|
|
be42bcf8eb | ||
|
|
b3fc357ecf | ||
|
|
5ba953ecd0 | ||
|
|
13121a907c | ||
|
|
648bf9a097 | ||
|
|
24340b0692 |
85
.circleci/config.yml
Normal file
85
.circleci/config.yml
Normal file
@@ -0,0 +1,85 @@
|
||||
# Javascript Node CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
||||
#
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: circleci/node:11.14.0
|
||||
|
||||
working_directory: ~/semaphore_private/
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/semaphore_private/
|
||||
|
||||
- run:
|
||||
name: Install solc
|
||||
command: wget https://github.com/ethereum/solidity/releases/download/v0.5.12/solc-static-linux && chmod a+x solc-static-linux && sudo mv solc-static-linux /usr/bin/solc
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1.9-dependencies-{{ checksum "package-lock.json" }}
|
||||
|
||||
- run: npm install
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1.9-dependencies-{{ checksum "package-lock.json" }}
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1.9-dependencies-{{ checksum "contracts/package-lock.json" }}-{{ checksum "circuits/package-lock.json" }}-{{ checksum "config/package-lock.json" }}
|
||||
|
||||
- run: npm run bootstrap && npm run build
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- contracts/node_modules
|
||||
- config/node_modules
|
||||
- circuits/node_modules
|
||||
key: v1.8-dependencies-{{ checksum "contracts/package-lock.json" }}-{{ checksum "circuits/package-lock.json" }}-{{ checksum "config/package-lock.json" }}
|
||||
|
||||
# checksum the snarks definitions
|
||||
- run:
|
||||
name: Checksum snark files
|
||||
command: cd circuits/ && ./scripts/checksum_snarks.sh
|
||||
|
||||
- restore_cache:
|
||||
name: restore-snark-cache
|
||||
keys:
|
||||
- v1.9-dependencies-{{ checksum "circuits/build/.snark_checksum" }}
|
||||
|
||||
# build snarks
|
||||
- run:
|
||||
name: Build snark files
|
||||
command: cd circuits && ./scripts/build_snarks.sh
|
||||
no_output_timeout: 600m
|
||||
|
||||
# cache generated snark circuit and keys
|
||||
- save_cache:
|
||||
key: v1.9-dependencies-{{ checksum "circuits/build/.snark_checksum" }}
|
||||
paths:
|
||||
- circuits/build/circuit.json
|
||||
- circuits/build/proving_key.bin
|
||||
- circuits/build/proving_key.json
|
||||
- circuits/build/verification_key.json
|
||||
- circuits/build/verifier.sol
|
||||
|
||||
- run:
|
||||
name: Compile contracts
|
||||
command: cd contracts && npm run compileSol
|
||||
|
||||
- run:
|
||||
name: Run circuit tests
|
||||
command: cd circuits && ./scripts/runTestsInCircleCi.sh
|
||||
|
||||
- run:
|
||||
name: Run contract tests
|
||||
command: cd contracts && ./scripts/runTestsInCircleCi.sh
|
||||
|
||||
- store_artifacts:
|
||||
path: circuits/build
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["@commitlint/config-conventional"]
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -1,7 +0,0 @@
|
||||
DEFAULT_NETWORK=hardhat
|
||||
TREE_DEPTH=20
|
||||
REPORT_GAS=false
|
||||
BACKEND_PRIVATE_KEY=
|
||||
INFURA_API_KEY=
|
||||
COINMARKETCAP_API_KEY=
|
||||
ETHERSCAN_API_KEY=
|
||||
@@ -1,31 +0,0 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# hardhat
|
||||
cache
|
||||
|
||||
# types
|
||||
types
|
||||
|
||||
# circuits
|
||||
circuits
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "tsconfig.json"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"]
|
||||
}
|
||||
18
.github/ISSUE_TEMPLATE/----network.md
vendored
18
.github/ISSUE_TEMPLATE/----network.md
vendored
@@ -1,18 +0,0 @@
|
||||
---
|
||||
name: "\U0001F578️ Network"
|
||||
about: Propose a new network in which to deploy Semaphore contracts
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Provide data related to the network**
|
||||
|
||||
* Name
|
||||
* Chain id
|
||||
* URL
|
||||
|
||||
**Why are you using this network?**
|
||||
|
||||
Describe the reasons why you think it is important for Semaphore to be deployed on this network.
|
||||
22
.github/ISSUE_TEMPLATE/----project.md
vendored
22
.github/ISSUE_TEMPLATE/----project.md
vendored
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: " \U0001F4A0 Project"
|
||||
about: If you are using Semaphore we can help you share your project
|
||||
title: ''
|
||||
labels: "documentation \U0001F4D6"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe your project**
|
||||
A brief description of your project. In what way have you used Semaphore?
|
||||
|
||||
**Other info**
|
||||
|
||||
- Name
|
||||
- Icon
|
||||
|
||||
**Links**
|
||||
|
||||
- Website
|
||||
- Github
|
||||
- Socials
|
||||
34
.github/ISSUE_TEMPLATE/---bug.md
vendored
34
.github/ISSUE_TEMPLATE/---bug.md
vendored
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: "\U0001F41E Bug"
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: "bug \U0001F41B"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Technologies (please complete the following information):**
|
||||
|
||||
- Node.js version
|
||||
- NPM version
|
||||
- Solidity version
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/---feature.md
vendored
20
.github/ISSUE_TEMPLATE/---feature.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "\U0001F680 Feature"
|
||||
about: Suggest an idea for Semaphore
|
||||
title: ''
|
||||
labels: 'feature :rocket:'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
24
.github/pull_request_template.md
vendored
24
.github/pull_request_template.md
vendored
@@ -1,24 +0,0 @@
|
||||
<!-- Please refer to our contributing documentation for any questions on submitting a pull request -->
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Related Issue
|
||||
|
||||
<!--- This project accepts pull requests related to open issues -->
|
||||
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
|
||||
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
||||
<!--- Please link to the issue here: -->
|
||||
|
||||
## Does this introduce a breaking change?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
<!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
|
||||
## Other information
|
||||
|
||||
<!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
|
||||
50
.github/workflows/coverall.yml
vendored
50
.github/workflows/coverall.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: coverall
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
TREE_DEPTH: 20
|
||||
|
||||
jobs:
|
||||
coverall:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
|
||||
|
||||
- name: Restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: YARN_CHECKSUM_BEHAVIOR=ignore yarn
|
||||
|
||||
- name: Download Snark artifacts
|
||||
run: yarn download:snark-artifacts
|
||||
|
||||
- name: Compile contracts
|
||||
run: yarn compile
|
||||
|
||||
- name: Test contracts with coverage
|
||||
run: yarn test:coverage
|
||||
|
||||
- uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
44
.github/workflows/style.yml
vendored
44
.github/workflows/style.yml
vendored
@@ -1,44 +0,0 @@
|
||||
name: style
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
style:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
|
||||
|
||||
- name: Restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: YARN_CHECKSUM_BEHAVIOR=ignore yarn
|
||||
|
||||
- name: Run Prettier
|
||||
run: yarn prettier
|
||||
|
||||
- name: Run Eslint
|
||||
run: yarn lint
|
||||
|
||||
- name: Compile contracts
|
||||
run: yarn compile
|
||||
47
.github/workflows/test.yml
vendored
47
.github/workflows/test.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
TREE_DEPTH: 20
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
|
||||
|
||||
- name: Restore yarn cache
|
||||
uses: actions/cache@v3
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
run: YARN_CHECKSUM_BEHAVIOR=ignore yarn
|
||||
|
||||
- name: Download Snark artifacts
|
||||
run: yarn download:snark-artifacts
|
||||
|
||||
- name: Compile contracts
|
||||
run: yarn compile
|
||||
|
||||
- name: Test contracts with coverage
|
||||
run: yarn test:coverage
|
||||
82
.gitignore
vendored
82
.gitignore
vendored
@@ -1,74 +1,8 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# Production
|
||||
build
|
||||
dist
|
||||
deployed-contracts/undefined.json
|
||||
deployed-contracts/localhost.json
|
||||
|
||||
# Hardhat
|
||||
artifacts
|
||||
cache
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v3
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
docs_src/book/
|
||||
.etherlime-store
|
||||
**/build/
|
||||
circuits/build
|
||||
contracts/compiled
|
||||
node_modules
|
||||
blake2sdef.json
|
||||
build/.snark_checksum
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"**/*.{js,ts}": ["prettier --write", "eslint --fix"]
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
# dependencies
|
||||
node_modules
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# testing
|
||||
coverage
|
||||
coverage.json
|
||||
|
||||
# hardhat
|
||||
cache
|
||||
|
||||
# types
|
||||
types
|
||||
|
||||
# circuits
|
||||
circuits
|
||||
|
||||
# contracts
|
||||
contracts/verifiers
|
||||
|
||||
# production
|
||||
dist
|
||||
build
|
||||
|
||||
# github
|
||||
.github/ISSUE_TEMPLATE
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"semi": false,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none"
|
||||
}
|
||||
21
.solcover.js
21
.solcover.js
@@ -1,21 +0,0 @@
|
||||
module.exports = {
|
||||
skipFiles: [
|
||||
"verifiers/Verifier16.sol",
|
||||
"verifiers/Verifier17.sol",
|
||||
"verifiers/Verifier18.sol",
|
||||
"verifiers/Verifier19.sol",
|
||||
"verifiers/Verifier20.sol",
|
||||
"verifiers/Verifier21.sol",
|
||||
"verifiers/Verifier22.sol",
|
||||
"verifiers/Verifier23.sol",
|
||||
"verifiers/Verifier24.sol",
|
||||
"verifiers/Verifier25.sol",
|
||||
"verifiers/Verifier26.sol",
|
||||
"verifiers/Verifier27.sol",
|
||||
"verifiers/Verifier28.sol",
|
||||
"verifiers/Verifier29.sol",
|
||||
"verifiers/Verifier30.sol",
|
||||
"verifiers/Verifier31.sol",
|
||||
"verifiers/Verifier32.sol"
|
||||
]
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"extends": "solhint:recommended",
|
||||
"plugins": ["prettier"],
|
||||
"rules": {
|
||||
"code-complexity": ["error", 7],
|
||||
"compiler-version": ["error", ">=0.8.0"],
|
||||
"const-name-snakecase": "off",
|
||||
"no-empty-blocks": "off",
|
||||
"constructor-syntax": "error",
|
||||
"func-visibility": ["error", { "ignoreConstructors": true }],
|
||||
"max-line-length": ["error", 120],
|
||||
"not-rely-on-time": "off",
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
],
|
||||
"reason-string": ["warn", { "maxLength": 80 }]
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
contracts/verifiers
|
||||
1
.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id
vendored
1
.yarn/releases/yarn-3.2.1.cjs.REMOVED.git-id
vendored
@@ -1 +0,0 @@
|
||||
b3cadff6efb37a12712d12c2553ec703dbcaa4dd
|
||||
@@ -1,4 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
checksumBehavior: update
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.1.cjs
|
||||
@@ -1,127 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
111
CONTRIBUTING.md
111
CONTRIBUTING.md
@@ -1,111 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
:tada: Thank you for being interested in contributing to the Semaphore project! :tada:
|
||||
|
||||
Feel welcome and read the following sections in order to know how to ask questions and how to work on something.
|
||||
|
||||
All members of our community are expected to follow our [Code of Conduct](/CODE_OF_CONDUCT.md). Please make sure you are welcoming and friendly in all of our spaces.
|
||||
|
||||
We're really glad you're reading this, because we need volunteer developers to help this project come to fruition. 👏
|
||||
|
||||
## Issues
|
||||
|
||||
The best way to contribute to our projects is by opening a [new issue](https://github.com/semaphore-protocol/semaphore/issues/new/choose) or tackling one of the issues listed [here](https://github.com/semaphore-protocol/semaphore/contribute).
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Pull requests are great if you want to add a feature or fix a bug. Here's a quick guide:
|
||||
|
||||
1. Fork the repo.
|
||||
|
||||
2. Run the tests. We only take pull requests with passing tests.
|
||||
|
||||
3. Add a test for your change. Only refactoring and documentation changes require no new tests.
|
||||
|
||||
4. Make sure to check out the [Style Guide](/CONTRIBUTING#style-guide) and ensure that your code complies with the rules.
|
||||
|
||||
5. Make the test pass.
|
||||
|
||||
6. Commit your changes.
|
||||
|
||||
7. Push to your fork and submit a pull request on our `dev` branch. Please provide us with some explanation of why you made the changes you made. For new features make sure to explain a standard use case to us.
|
||||
|
||||
## CI (Github Actions) Tests
|
||||
|
||||
We use GitHub Actions to test each PR before it is merged.
|
||||
|
||||
When you submit your PR (or later change that code), a CI build will automatically be kicked off. A note will be added to the PR, and will indicate the current status of the build.
|
||||
|
||||
## Style Guide
|
||||
|
||||
### Code rules
|
||||
|
||||
We always use ESLint and Prettier. To check that your code follows the rules, simply run the npm script `yarn lint`.
|
||||
|
||||
### Commits rules
|
||||
|
||||
For commits it is recommended to use [Conventional Commits](https://www.conventionalcommits.org).
|
||||
|
||||
Don't worry if it looks complicated, in our repositories, after `git add`, you can usually run the npm script `yarn commit` to make many of these steps interactive.
|
||||
|
||||
Each commit message consists of a **header**, a **body** and a **footer**. The **header** has a special format that includes a **type**, a **scope** and a **subject**:
|
||||
|
||||
<type>(<scope>): <subject>
|
||||
<BLANK LINE>
|
||||
<body>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
|
||||
The **header** is mandatory and the **scope** of the header is optional.
|
||||
|
||||
#### Type
|
||||
|
||||
The type must be one of the following:
|
||||
|
||||
- feat: A new feature
|
||||
- fix: A bug fix
|
||||
- docs: Documentation only changes
|
||||
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
|
||||
- refactor: A code change that neither fixes a bug nor adds a feature (improvements of the code structure)
|
||||
- perf: A code change that improves the performance
|
||||
- test: Adding missing or correcting existing tests
|
||||
- build: Changes that affect the build system or external dependencies (example scopes: gulp, npm)
|
||||
- ci: Changes to CI configuration files and scripts (example scopes: travis, circle)
|
||||
- chore: Other changes that don't modify src or test files
|
||||
- revert: Reverts a previous commit
|
||||
|
||||
#### Scope
|
||||
|
||||
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages).
|
||||
|
||||
#### Subject
|
||||
|
||||
The subject contains a succinct description of the change:
|
||||
|
||||
- Use the imperative, present tense: "change" not "changed" nor "changes"
|
||||
- Don't capitalize the first letter
|
||||
- No dot (.) at the end
|
||||
|
||||
#### Body
|
||||
|
||||
Just as in the subject, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior.
|
||||
|
||||
### Branch rules
|
||||
|
||||
- There must be a `main` branch, used only for the releases.
|
||||
- There must be a `dev` branch, used to merge all the branches under it.
|
||||
- Avoid long descriptive names for long-lived branches.
|
||||
- Use kebab-case (no CamelCase).
|
||||
- Use grouping tokens (words) at the beginning of your branch names (in a similar way to the `type` of commit).
|
||||
- Define and use short lead tokens to differentiate branches in a way that is meaningful to your workflow.
|
||||
- Use slashes to separate parts of your branch names.
|
||||
- Remove branch after merge if it is not important.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
git branch -b docs/readme
|
||||
git branch -b test/a-feature
|
||||
git branch -b feat/sidebar
|
||||
git branch -b fix/b-feature
|
||||
```
|
||||
21
LICENSE
21
LICENSE
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Ethereum Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
189
README.md
189
README.md
@@ -1,183 +1,12 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon-dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon.svg">
|
||||
<img width="40" alt="Semaphore icon." src="https://github.com/semaphore-protocol/website/blob/main/static/img/semaphore-icon.svg">
|
||||
</picture>
|
||||
Semaphore
|
||||
</h1>
|
||||
</p>
|
||||
# Semaphore
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol" target="_blank">
|
||||
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/actions?query=workflow%3Atest">
|
||||
<img alt="GitHub Workflow test" src="https://img.shields.io/github/workflow/status/semaphore-protocol/semaphore/test?label=test&style=flat-square&logo=github">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/actions?query=workflow%3Astyle">
|
||||
<img alt="GitHub Workflow style" src="https://img.shields.io/github/workflow/status/semaphore-protocol/semaphore/style?label=style&style=flat-square&logo=github">
|
||||
</a>
|
||||
<a href="https://coveralls.io/github/semaphore-protocol/semaphore">
|
||||
<img alt="Coveralls" src="https://img.shields.io/coveralls/github/semaphore-protocol/semaphore?style=flat-square&logo=coveralls">
|
||||
</a>
|
||||
<a href="https://eslint.org/">
|
||||
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint">
|
||||
</a>
|
||||
<a href="https://prettier.io/">
|
||||
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier">
|
||||
</a>
|
||||
<img alt="Repository top language" src="https://img.shields.io/github/languages/top/semaphore-protocol/semaphore?style=flat-square">
|
||||
</p>
|
||||
Semaphore is a zero-knowledge gadget which allows users to prove their
|
||||
membership of a set without revealing their original identity. At the same
|
||||
time, it allows users to signal their endorsement of an arbitrary string. It is
|
||||
designed to be a simple and generic privacy layer for Ethereum dApps. Use cases
|
||||
include private voting, whistleblowing, mixers, and anonymous authentication.
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
For more information, refer to the
|
||||
[documentation](https://appliedzkp.github.io/semaphore/).
|
||||
|
||||
| Semaphore is a protocol, designed to be a simple and generic privacy layer for Ethereum DApps. Using zero knowledge, Ethereum users can prove their membership of a group and send signals such as votes or endorsements without revealing their original identity. |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
The core of the Semaphore protocol is in the [circuit logic](/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/contracts) (NPM: `@semaphore-protocol/contracts`) and [JavaScript libraries](https://github.com/semaphore-protocol/semaphore.js) to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
|
||||
|
||||
You can find Semaphore V1 on [`version/1.0.0`](https://github.com/semaphore-protocol/semaphore/tree/version/1.0.0).
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
Clone this repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/semaphore-protocol/semaphore.git
|
||||
```
|
||||
|
||||
and install the dependencies:
|
||||
|
||||
```bash
|
||||
cd semaphore && yarn
|
||||
```
|
||||
|
||||
## 📜 Usage
|
||||
|
||||
Copy the `.env.example` file as `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
and add your environment variables.
|
||||
|
||||
### Code quality and formatting
|
||||
|
||||
Run [ESLint](https://eslint.org/) to analyze the code and catch bugs:
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
Run [Prettier](https://prettier.io/) to check formatting rules:
|
||||
|
||||
```bash
|
||||
yarn prettier
|
||||
```
|
||||
|
||||
or to automatically format the code:
|
||||
|
||||
```bash
|
||||
yarn prettier:write
|
||||
```
|
||||
|
||||
### Conventional commits
|
||||
|
||||
Semaphore uses [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). A [command line utility](https://github.com/commitizen/cz-cli) to commit using the correct syntax can be used by running:
|
||||
|
||||
```bash
|
||||
yarn commit
|
||||
```
|
||||
|
||||
It will also automatically check that the modified files comply with ESLint and Prettier rules.
|
||||
|
||||
### Snark artifacts
|
||||
|
||||
Download the Semaphore snark artifacts needed to generate and verify proofs:
|
||||
|
||||
```bash
|
||||
yarn download:snark-artifacts
|
||||
```
|
||||
|
||||
### Compile contracts
|
||||
|
||||
Compile the smart contracts with [Hardhat](https://hardhat.org/):
|
||||
|
||||
```bash
|
||||
yarn compile
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
Run [Mocha](https://mochajs.org/) to test the contracts:
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
|
||||
You can also generate a test coverage report:
|
||||
|
||||
```bash
|
||||
yarn test:coverage
|
||||
```
|
||||
|
||||
or a test gas report:
|
||||
|
||||
```bash
|
||||
yarn test:report-gas
|
||||
```
|
||||
|
||||
### Deploy contracts
|
||||
|
||||
Deploy a verifier contract with depth = 20:
|
||||
|
||||
```bash
|
||||
yarn deploy:verifier --depth 20
|
||||
```
|
||||
|
||||
Deploy the `Semaphore.sol` contract with one verifier:
|
||||
|
||||
```bash
|
||||
yarn deploy:semaphore --verifiers '[{"merkleTreeDepth": 20, "contractAddress": "0x06bcD633988c1CE7Bd134DbE2C12119b6f3E4bD1"}]'
|
||||
```
|
||||
|
||||
Deploy all verifiers and Semaphore contract:
|
||||
|
||||
```bash
|
||||
yarn deploy:all
|
||||
```
|
||||
|
||||
If you want to deploy contracts in a specific network you can set up the `DEFAULT_NETWORK` variable in your `.env` file with the name of one of our supported networks (hardhat, localhost, goerli, arbitrum). Or you can specify it as option:
|
||||
|
||||
```bash
|
||||
yarn deploy:all --network goerli
|
||||
yarn deploy:all --network localhost
|
||||
```
|
||||
|
||||
If you want to deploy contracts on Goerli or Arbitrum, remember to provide a valid private key and an Infura API in your `.env` file.
|
||||
Join the [Telegram group](https://t.me/joinchat/B-PQx1U3GtAh--Z4Fwo56A) to discuss.
|
||||
|
||||
392
circuits/circom/blake2s/blake2s.circom
Normal file
392
circuits/circom/blake2s/blake2s.circom
Normal file
@@ -0,0 +1,392 @@
|
||||
// based on https://github.com/zcash/librustzcash/blob/master/sapling-crypto/src/circuit/blake2s.rs
|
||||
|
||||
include "uint32.circom";
|
||||
include "../../node_modules/circomlib/circuits/bitify.circom";
|
||||
include "../../node_modules/circomlib/circuits/sha256/rotate.circom";
|
||||
|
||||
template MixingG(a, b, c, d) {
|
||||
signal input in_v[16][32];
|
||||
signal input x[32];
|
||||
signal input y[32];
|
||||
|
||||
signal output out_v[16][32];
|
||||
|
||||
component v_a_add = Uint32Add(3);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_a_add.nums_bits[0][i] <== in_v[a][i];
|
||||
v_a_add.nums_bits[1][i] <== in_v[b][i];
|
||||
v_a_add.nums_bits[2][i] <== x[i];
|
||||
}
|
||||
|
||||
component v_d_a_xor = Uint32Xor();
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_d_a_xor.a_bits[i] <== in_v[d][i];
|
||||
v_d_a_xor.b_bits[i] <== v_a_add.out_bits[i];
|
||||
}
|
||||
|
||||
component v_d_a_rot = RotR(32, 16);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_d_a_rot.in[i] <== v_d_a_xor.out_bits[i];
|
||||
}
|
||||
|
||||
component v_c_add = Uint32Add(2);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_c_add.nums_bits[0][i] <== in_v[c][i];
|
||||
v_c_add.nums_bits[1][i] <== v_d_a_rot.out[i];
|
||||
}
|
||||
|
||||
component v_b_c_xor = Uint32Xor();
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_b_c_xor.a_bits[i] <== in_v[b][i];
|
||||
v_b_c_xor.b_bits[i] <== v_c_add.out_bits[i];
|
||||
}
|
||||
|
||||
component v_b_c_rot = RotR(32, 12);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_b_c_rot.in[i] <== v_b_c_xor.out_bits[i];
|
||||
}
|
||||
|
||||
component v_a_add_2 = Uint32Add(3);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_a_add_2.nums_bits[0][i] <== v_a_add.out_bits[i];
|
||||
v_a_add_2.nums_bits[1][i] <== v_b_c_rot.out[i];
|
||||
v_a_add_2.nums_bits[2][i] <== y[i];
|
||||
}
|
||||
|
||||
component v_d_a_xor_2 = Uint32Xor();
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_d_a_xor_2.a_bits[i] <== v_d_a_rot.out[i];
|
||||
v_d_a_xor_2.b_bits[i] <== v_a_add_2.out_bits[i];
|
||||
}
|
||||
|
||||
component v_d_a_rot_2 = RotR(32, 8);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_d_a_rot_2.in[i] <== v_d_a_xor_2.out_bits[i];
|
||||
}
|
||||
|
||||
component v_c_add_2 = Uint32Add(2);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_c_add_2.nums_bits[0][i] <== v_c_add.out_bits[i];
|
||||
v_c_add_2.nums_bits[1][i] <== v_d_a_rot_2.out[i];
|
||||
}
|
||||
|
||||
component v_b_c_xor_2 = Uint32Xor();
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_b_c_xor_2.a_bits[i] <== v_b_c_rot.out[i];
|
||||
v_b_c_xor_2.b_bits[i] <== v_c_add_2.out_bits[i];
|
||||
}
|
||||
|
||||
component v_b_c_rot_2 = RotR(32, 7);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
v_b_c_rot_2.in[i] <== v_b_c_xor_2.out_bits[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < 16; i++) {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
if (i == a) {
|
||||
out_v[i][j] <== v_a_add_2.out_bits[j];
|
||||
} else if (i == b) {
|
||||
out_v[i][j] <== v_b_c_rot_2.out[j];
|
||||
} else if (i == c) {
|
||||
out_v[i][j] <== v_c_add_2.out_bits[j];
|
||||
} else if (i == d) {
|
||||
out_v[i][j] <== v_d_a_rot_2.out[j];
|
||||
} else {
|
||||
out_v[i][j] <== in_v[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template Blake2sCompression(t, f) {
|
||||
signal input in_h[8][32];
|
||||
signal input in_m[16][32];
|
||||
|
||||
signal output out_h[8][32];
|
||||
|
||||
var v_consts = [
|
||||
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19
|
||||
];
|
||||
signal v_h[16][32];
|
||||
|
||||
for (var i = 0; i < 16; i++) {
|
||||
if (i < 8) {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_h[i][j] <== in_h[i][j];
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_h[i][j] <== (v_consts[i - 8] >> j) & 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal v_pass_1[16][32];
|
||||
component v_12_xor = Uint32Xor();
|
||||
component v_13_xor = Uint32Xor();
|
||||
component v_14_xor = Uint32Xor();
|
||||
|
||||
for (var i = 0; i < 16; i++) {
|
||||
if (i == 12) {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_12_xor.a_bits[j] <== v_h[i][j];
|
||||
v_12_xor.b_bits[j] <== (t >> j) & 1;
|
||||
}
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_pass_1[i][j] <== v_12_xor.out_bits[j];
|
||||
}
|
||||
} else if (i == 13) {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_13_xor.a_bits[j] <== v_h[i][j];
|
||||
v_13_xor.b_bits[j] <== (t >> (32 + j)) & 1;
|
||||
}
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_pass_1[i][j] <== v_13_xor.out_bits[j];
|
||||
}
|
||||
} else if ((i == 14)) {
|
||||
if (f == 1) {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_14_xor.a_bits[j] <== v_h[i][j];
|
||||
v_14_xor.b_bits[j] <== 1;
|
||||
}
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_pass_1[i][j] <== v_14_xor.out_bits[j];
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_14_xor.a_bits[j] <== v_h[i][j];
|
||||
v_14_xor.b_bits[j] <== 0;
|
||||
}
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_pass_1[i][j] <== v_h[i][j];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
v_pass_1[i][j] <== v_h[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sigma = [
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
||||
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
|
||||
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
|
||||
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
|
||||
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
|
||||
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
|
||||
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
|
||||
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
|
||||
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]
|
||||
];
|
||||
|
||||
component mixing_g[10][8];
|
||||
var s;
|
||||
for (var i = 0; i < 10; i++) {
|
||||
s = sigma[i];
|
||||
mixing_g[i][0] = MixingG(0, 4, 8, 12);
|
||||
for (var j = 0; j < 16; j++) {
|
||||
for (var k = 0; k < 32; k++) {
|
||||
if (i == 0) {
|
||||
mixing_g[i][0].in_v[j][k] <== v_pass_1[j][k];
|
||||
} else {
|
||||
mixing_g[i][0].in_v[j][k] <== mixing_g[i - 1][7].out_v[j][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var k = 0; k < 32; k++) {
|
||||
mixing_g[i][0].x[k] <== in_m[s[0]][k];
|
||||
mixing_g[i][0].y[k] <== in_m[s[1]][k];
|
||||
}
|
||||
|
||||
|
||||
mixing_g[i][1] = MixingG(1, 5, 9, 13);
|
||||
for (var k = 0; k < 32; k++) {
|
||||
for (var j = 0; j < 16; j++) {
|
||||
mixing_g[i][1].in_v[j][k] <== mixing_g[i][0].out_v[j][k];
|
||||
}
|
||||
mixing_g[i][1].x[k] <== in_m[s[2]][k];
|
||||
mixing_g[i][1].y[k] <== in_m[s[3]][k];
|
||||
}
|
||||
|
||||
mixing_g[i][2] = MixingG(2, 6, 10, 14);
|
||||
for (var k = 0; k < 32; k++) {
|
||||
for (var j = 0; j < 16; j++) {
|
||||
mixing_g[i][2].in_v[j][k] <== mixing_g[i][1].out_v[j][k];
|
||||
}
|
||||
mixing_g[i][2].x[k] <== in_m[s[4]][k];
|
||||
mixing_g[i][2].y[k] <== in_m[s[5]][k];
|
||||
}
|
||||
|
||||
mixing_g[i][3] = MixingG(3, 7, 11, 15);
|
||||
for (var k = 0; k < 32; k++) {
|
||||
for (var j = 0; j < 16; j++) {
|
||||
mixing_g[i][3].in_v[j][k] <== mixing_g[i][2].out_v[j][k];
|
||||
}
|
||||
mixing_g[i][3].x[k] <== in_m[s[6]][k];
|
||||
mixing_g[i][3].y[k] <== in_m[s[7]][k];
|
||||
}
|
||||
|
||||
mixing_g[i][4] = MixingG(0, 5, 10, 15);
|
||||
for (var k = 0; k < 32; k++) {
|
||||
for (var j = 0; j < 16; j++) {
|
||||
mixing_g[i][4].in_v[j][k] <== mixing_g[i][3].out_v[j][k];
|
||||
}
|
||||
mixing_g[i][4].x[k] <== in_m[s[8]][k];
|
||||
mixing_g[i][4].y[k] <== in_m[s[9]][k];
|
||||
}
|
||||
|
||||
mixing_g[i][5] = MixingG(1, 6, 11, 12);
|
||||
for (var k = 0; k < 32; k++) {
|
||||
for (var j = 0; j < 16; j++) {
|
||||
mixing_g[i][5].in_v[j][k] <== mixing_g[i][4].out_v[j][k];
|
||||
}
|
||||
mixing_g[i][5].x[k] <== in_m[s[10]][k];
|
||||
mixing_g[i][5].y[k] <== in_m[s[11]][k];
|
||||
}
|
||||
|
||||
mixing_g[i][6] = MixingG(2, 7, 8, 13);
|
||||
for (var k = 0; k < 32; k++) {
|
||||
for (var j = 0; j < 16; j++) {
|
||||
mixing_g[i][6].in_v[j][k] <== mixing_g[i][5].out_v[j][k];
|
||||
}
|
||||
mixing_g[i][6].x[k] <== in_m[s[12]][k];
|
||||
mixing_g[i][6].y[k] <== in_m[s[13]][k];
|
||||
}
|
||||
|
||||
mixing_g[i][7] = MixingG(3, 4, 9, 14);
|
||||
for (var k = 0; k < 32; k++) {
|
||||
for (var j = 0; j < 16; j++) {
|
||||
mixing_g[i][7].in_v[j][k] <== mixing_g[i][6].out_v[j][k];
|
||||
}
|
||||
mixing_g[i][7].x[k] <== in_m[s[14]][k];
|
||||
mixing_g[i][7].y[k] <== in_m[s[15]][k];
|
||||
}
|
||||
}
|
||||
|
||||
component h_xor_1[8];
|
||||
component h_xor_2[8];
|
||||
for (var i = 0; i < 8; i++) {
|
||||
h_xor_1[i] = Uint32Xor();
|
||||
h_xor_2[i] = Uint32Xor();
|
||||
for (var j = 0; j < 32; j++) {
|
||||
h_xor_1[i].a_bits[j] <== in_h[i][j];
|
||||
h_xor_1[i].b_bits[j] <== mixing_g[9][7].out_v[i][j];
|
||||
}
|
||||
|
||||
for (var j = 0; j < 32; j++) {
|
||||
h_xor_2[i].a_bits[j] <== h_xor_1[i].out_bits[j];
|
||||
h_xor_2[i].b_bits[j] <== mixing_g[9][7].out_v[i + 8][j];
|
||||
}
|
||||
|
||||
for (var j = 0; j < 32; j++) {
|
||||
out_h[i][j] <== h_xor_2[i].out_bits[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template Blake2s(n_bits, personalization) {
|
||||
signal input in_bits[n_bits];
|
||||
signal output out[256];
|
||||
|
||||
signal h[8][32];
|
||||
component h_from_bits[8];
|
||||
component h6_xor;
|
||||
component h7_xor;
|
||||
|
||||
var h_consts = [
|
||||
0x6A09E667 ^ 0x01010000 ^ 32,
|
||||
0xBB67AE85,
|
||||
0x3C6EF372,
|
||||
0xA54FF53A,
|
||||
0x510E527F,
|
||||
0x9B05688C,
|
||||
0x1F83D9AB,
|
||||
0x5BE0CD19
|
||||
];
|
||||
|
||||
for (var i = 0; i < 8; i++) {
|
||||
h_from_bits[i] = Num2Bits(32);
|
||||
h_from_bits[i].in <== h_consts[i];
|
||||
if (i == 6) {
|
||||
h6_xor = Uint32Xor();
|
||||
for (var j = 0; j < 32; j++) {
|
||||
h6_xor.a_bits[j] <== h_from_bits[i].out[j];
|
||||
h6_xor.b_bits[j] <== (personalization >> j) & 1;
|
||||
}
|
||||
for (var j = 0; j < 32; j++) {
|
||||
h[i][j] <== h6_xor.out_bits[j];
|
||||
}
|
||||
} else if (i == 7) {
|
||||
h7_xor = Uint32Xor();
|
||||
for (var j = 0; j < 32; j++) {
|
||||
h7_xor.a_bits[j] <== h_from_bits[i].out[j];
|
||||
h7_xor.b_bits[j] <== (personalization >> (32 + j)) & 1;
|
||||
}
|
||||
for (var j = 0; j < 32; j++) {
|
||||
h[i][j] <== h7_xor.out_bits[j];
|
||||
}
|
||||
} else {
|
||||
for (var j = 0; j < 32; j++) {
|
||||
h[i][j] <== h_from_bits[i].out[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var n_rounded;
|
||||
if ( (n_bits % 512) == 0) {
|
||||
n_rounded = n_bits;
|
||||
} else {
|
||||
n_rounded = n_bits + (512 - (n_bits % 512));
|
||||
}
|
||||
var num_blocks = n_rounded / 512;
|
||||
if (num_blocks == 0) {
|
||||
num_blocks = 1;
|
||||
}
|
||||
|
||||
var n_rounded_bytes;
|
||||
if ( (n_bits % 8) == 0) {
|
||||
n_rounded_bytes = n_bits;
|
||||
} else {
|
||||
n_rounded_bytes = n_bits + (8 - (n_bits % 8));
|
||||
}
|
||||
|
||||
component compressions[num_blocks];
|
||||
var current_bit = 0;
|
||||
for (var i = 0; i < num_blocks; i++) {
|
||||
if (i < (num_blocks - 1)) {
|
||||
compressions[i] = Blake2sCompression((i + 1)*64, 0);
|
||||
} else {
|
||||
compressions[i] = Blake2sCompression(n_rounded_bytes/8, 1);
|
||||
}
|
||||
for (var j = 0; j < 32; j++) {
|
||||
for (var k = 0; k < 8; k++) {
|
||||
if (i == 0) {
|
||||
compressions[i].in_h[k][j] <== h[k][j];
|
||||
} else {
|
||||
compressions[i].in_h[k][j] <== compressions[i - 1].out_h[k][j];
|
||||
}
|
||||
}
|
||||
for (var l = 0; l < 16; l++) {
|
||||
current_bit = 512*i + 32*l + j;
|
||||
if (current_bit < n_bits) {
|
||||
compressions[i].in_m[l][j] <== in_bits[current_bit];
|
||||
} else {
|
||||
compressions[i].in_m[l][j] <== 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i == (num_blocks - 1)) {
|
||||
for (var j = 0; j < 8; j++) {
|
||||
for (var k = 0; k < 4; k++) {
|
||||
for (var l = 0; l < 8; l++) {
|
||||
out[32*j + 8*k + l] <== compressions[num_blocks - 1].out_h[8 - 1 - j][(4 - 1 - k)*8 + l];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
circuits/circom/blake2s/uint32.circom
Normal file
30
circuits/circom/blake2s/uint32.circom
Normal file
@@ -0,0 +1,30 @@
|
||||
include "../../node_modules/circomlib/circuits/binsum.circom";
|
||||
|
||||
template Uint32Add(n) {
|
||||
signal input nums_bits[n][32];
|
||||
signal output out_bits[32];
|
||||
|
||||
component sum = BinSum(32, n);
|
||||
var i;
|
||||
var j;
|
||||
for (i = 0; i < n; i++) {
|
||||
for (j = 0; j < 32; j++) {
|
||||
sum.in[i][j] <== nums_bits[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < 32; j++) {
|
||||
out_bits[j] <== sum.out[j];
|
||||
}
|
||||
}
|
||||
|
||||
template Uint32Xor() {
|
||||
signal input a_bits[32];
|
||||
signal input b_bits[32];
|
||||
|
||||
signal output out_bits[32];
|
||||
|
||||
for (var i = 0; i < 32; i++) {
|
||||
out_bits[i] <== a_bits[i] + b_bits[i] - 2*a_bits[i]*b_bits[i];
|
||||
}
|
||||
}
|
||||
258
circuits/circom/semaphore-base.circom
Normal file
258
circuits/circom/semaphore-base.circom
Normal file
@@ -0,0 +1,258 @@
|
||||
include "../node_modules/circomlib/circuits/pedersen.circom";
|
||||
include "../node_modules/circomlib/circuits/mimcsponge.circom";
|
||||
include "../node_modules/circomlib/circuits/bitify.circom";
|
||||
include "../node_modules/circomlib/circuits/eddsamimcsponge.circom";
|
||||
include "../node_modules/circomlib/circuits/babyjub.circom";
|
||||
include "../node_modules/circomlib/circuits/mux1.circom";
|
||||
include "../node_modules/circomlib/circuits/assert.circom";
|
||||
include "./blake2s/blake2s.circom";
|
||||
|
||||
template HashLeftRight() {
|
||||
signal input left;
|
||||
signal input right;
|
||||
|
||||
signal output hash;
|
||||
|
||||
component hasher = MiMCSponge(2, 1);
|
||||
left ==> hasher.ins[0];
|
||||
right ==> hasher.ins[1];
|
||||
hasher.k <== 0;
|
||||
|
||||
hash <== hasher.outs[0];
|
||||
}
|
||||
|
||||
template Selector() {
|
||||
signal input input_elem;
|
||||
signal input path_elem;
|
||||
signal input path_index;
|
||||
|
||||
signal output left;
|
||||
signal output right;
|
||||
|
||||
path_index * (1-path_index) === 0
|
||||
|
||||
component mux = MultiMux1(2);
|
||||
mux.c[0][0] <== input_elem;
|
||||
mux.c[0][1] <== path_elem;
|
||||
|
||||
mux.c[1][0] <== path_elem;
|
||||
mux.c[1][1] <== input_elem;
|
||||
|
||||
mux.s <== path_index;
|
||||
|
||||
left <== mux.out[0];
|
||||
right <== mux.out[1];
|
||||
}
|
||||
|
||||
template MerkleTreeInclusionProof(n_levels) {
|
||||
signal input identity_commitment;
|
||||
signal input identity_path_index[n_levels];
|
||||
signal input identity_path_elements[n_levels];
|
||||
signal output root;
|
||||
|
||||
component selectors[n_levels];
|
||||
component hashers[n_levels];
|
||||
|
||||
for (var i = 0; i < n_levels; i++) {
|
||||
selectors[i] = Selector();
|
||||
hashers[i] = HashLeftRight();
|
||||
|
||||
identity_path_index[i] ==> selectors[i].path_index;
|
||||
identity_path_elements[i] ==> selectors[i].path_elem;
|
||||
|
||||
selectors[i].left ==> hashers[i].left;
|
||||
selectors[i].right ==> hashers[i].right;
|
||||
}
|
||||
|
||||
identity_commitment ==> selectors[0].input_elem;
|
||||
|
||||
for (var i = 1; i < n_levels; i++) {
|
||||
hashers[i-1].hash ==> selectors[i].input_elem;
|
||||
}
|
||||
|
||||
root <== hashers[n_levels - 1].hash;
|
||||
}
|
||||
|
||||
template CalculateIdentityCommitment(IDENTITY_PK_SIZE_IN_BITS, NULLIFIER_TRAPDOOR_SIZE_IN_BITS) {
|
||||
signal input identity_pk[IDENTITY_PK_SIZE_IN_BITS];
|
||||
signal input identity_nullifier[NULLIFIER_TRAPDOOR_SIZE_IN_BITS];
|
||||
signal input identity_trapdoor[NULLIFIER_TRAPDOOR_SIZE_IN_BITS];
|
||||
|
||||
signal output out;
|
||||
|
||||
// identity commitment is a pedersen hash of (identity_pk, identity_nullifier, identity_trapdoor), each element padded up to 256 bits
|
||||
component identity_commitment = Pedersen(3*256);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
if (i < IDENTITY_PK_SIZE_IN_BITS) {
|
||||
identity_commitment.in[i] <== identity_pk[i];
|
||||
} else {
|
||||
identity_commitment.in[i] <== 0;
|
||||
}
|
||||
|
||||
if (i < NULLIFIER_TRAPDOOR_SIZE_IN_BITS) {
|
||||
identity_commitment.in[i + 256] <== identity_nullifier[i];
|
||||
identity_commitment.in[i + 2*256] <== identity_trapdoor[i];
|
||||
} else {
|
||||
identity_commitment.in[i + 256] <== 0;
|
||||
identity_commitment.in[i + 2*256] <== 0;
|
||||
}
|
||||
}
|
||||
|
||||
out <== identity_commitment.out[0];
|
||||
}
|
||||
|
||||
template CalculateNullifier(NULLIFIER_TRAPDOOR_SIZE_IN_BITS, EXTERNAL_NULLIFIER_SIZE_IN_BITS, n_levels) {
|
||||
signal input external_nullifier;
|
||||
signal input identity_nullifier[NULLIFIER_TRAPDOOR_SIZE_IN_BITS];
|
||||
signal input identity_path_index[n_levels];
|
||||
|
||||
signal output nullifiers_hash;
|
||||
|
||||
component external_nullifier_bits = Num2Bits(EXTERNAL_NULLIFIER_SIZE_IN_BITS);
|
||||
external_nullifier_bits.in <== external_nullifier;
|
||||
|
||||
var nullifiers_hasher_bits = NULLIFIER_TRAPDOOR_SIZE_IN_BITS + EXTERNAL_NULLIFIER_SIZE_IN_BITS + n_levels;
|
||||
if (nullifiers_hasher_bits < 512) {
|
||||
nullifiers_hasher_bits = 512;
|
||||
}
|
||||
assert (nullifiers_hasher_bits <= 512);
|
||||
|
||||
component nullifiers_hasher = Blake2s(nullifiers_hasher_bits, 0);
|
||||
for (var i = 0; i < NULLIFIER_TRAPDOOR_SIZE_IN_BITS; i++) {
|
||||
nullifiers_hasher.in_bits[i] <== identity_nullifier[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < EXTERNAL_NULLIFIER_SIZE_IN_BITS; i++) {
|
||||
nullifiers_hasher.in_bits[NULLIFIER_TRAPDOOR_SIZE_IN_BITS + i] <== external_nullifier_bits.out[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < n_levels; i++) {
|
||||
nullifiers_hasher.in_bits[NULLIFIER_TRAPDOOR_SIZE_IN_BITS + EXTERNAL_NULLIFIER_SIZE_IN_BITS + i] <== identity_path_index[i];
|
||||
}
|
||||
|
||||
for (var i = (NULLIFIER_TRAPDOOR_SIZE_IN_BITS + EXTERNAL_NULLIFIER_SIZE_IN_BITS + n_levels); i < nullifiers_hasher_bits; i++) {
|
||||
nullifiers_hasher.in_bits[i] <== 0;
|
||||
}
|
||||
|
||||
component nullifiers_hash_num = Bits2Num(250);
|
||||
for (var i = 0; i < 250; i++) {
|
||||
nullifiers_hash_num.in[i] <== nullifiers_hasher.out[i];
|
||||
}
|
||||
|
||||
nullifiers_hash <== nullifiers_hash_num.out;
|
||||
}
|
||||
|
||||
// n_levels must be < 32
|
||||
template Semaphore(n_levels) {
|
||||
// BEGIN signals
|
||||
|
||||
signal input signal_hash;
|
||||
signal input external_nullifier;
|
||||
|
||||
signal private input fake_zero;
|
||||
|
||||
// mimc vector commitment
|
||||
signal private input identity_pk[2];
|
||||
signal private input identity_nullifier;
|
||||
signal private input identity_trapdoor;
|
||||
signal private input identity_path_elements[n_levels];
|
||||
signal private input identity_path_index[n_levels];
|
||||
|
||||
// signature on (external nullifier, signal_hash) with identity_pk
|
||||
signal private input auth_sig_r[2];
|
||||
signal private input auth_sig_s;
|
||||
|
||||
// mimc hash
|
||||
signal output root;
|
||||
signal output nullifiers_hash;
|
||||
|
||||
// END signals
|
||||
|
||||
// BEGIN constants
|
||||
|
||||
var IDENTITY_PK_SIZE_IN_BITS = 254;
|
||||
var NULLIFIER_TRAPDOOR_SIZE_IN_BITS = 248;
|
||||
var EXTERNAL_NULLIFIER_SIZE_IN_BITS = 232;
|
||||
|
||||
// END constants
|
||||
|
||||
fake_zero === 0;
|
||||
|
||||
component verify_identity_pk_on_curve = BabyCheck();
|
||||
verify_identity_pk_on_curve.x <== identity_pk[0];
|
||||
verify_identity_pk_on_curve.y <== identity_pk[1];
|
||||
|
||||
component verify_auth_sig_r_on_curve = BabyCheck();
|
||||
verify_auth_sig_r_on_curve.x <== auth_sig_r[0];
|
||||
verify_auth_sig_r_on_curve.y <== auth_sig_r[1];
|
||||
|
||||
// get a prime subgroup element derived from identity_pk
|
||||
component dbl1 = BabyDbl();
|
||||
dbl1.x <== identity_pk[0];
|
||||
dbl1.y <== identity_pk[1];
|
||||
component dbl2 = BabyDbl();
|
||||
dbl2.x <== dbl1.xout;
|
||||
dbl2.y <== dbl1.yout;
|
||||
component dbl3 = BabyDbl();
|
||||
dbl3.x <== dbl2.xout;
|
||||
dbl3.y <== dbl2.yout;
|
||||
|
||||
component identity_nullifier_bits = Num2Bits(NULLIFIER_TRAPDOOR_SIZE_IN_BITS);
|
||||
identity_nullifier_bits.in <== identity_nullifier;
|
||||
|
||||
component identity_trapdoor_bits = Num2Bits(NULLIFIER_TRAPDOOR_SIZE_IN_BITS);
|
||||
identity_trapdoor_bits.in <== identity_trapdoor;
|
||||
|
||||
component identity_pk_0_bits = Num2Bits_strict();
|
||||
identity_pk_0_bits.in <== dbl3.xout;
|
||||
|
||||
// BEGIN identity commitment
|
||||
component identity_commitment = CalculateIdentityCommitment(IDENTITY_PK_SIZE_IN_BITS, NULLIFIER_TRAPDOOR_SIZE_IN_BITS);
|
||||
for (var i = 0; i < IDENTITY_PK_SIZE_IN_BITS; i++) {
|
||||
identity_commitment.identity_pk[i] <== identity_pk_0_bits.out[i];
|
||||
}
|
||||
for (var i = 0; i < NULLIFIER_TRAPDOOR_SIZE_IN_BITS; i++) {
|
||||
identity_commitment.identity_nullifier[i] <== identity_nullifier_bits.out[i];
|
||||
identity_commitment.identity_trapdoor[i] <== identity_trapdoor_bits.out[i];
|
||||
}
|
||||
// END identity commitment
|
||||
|
||||
// BEGIN tree
|
||||
component tree = MerkleTreeInclusionProof(n_levels);
|
||||
tree.identity_commitment <== identity_commitment.out;
|
||||
for (var i = 0; i < n_levels; i++) {
|
||||
tree.identity_path_index[i] <== identity_path_index[i];
|
||||
tree.identity_path_elements[i] <== identity_path_elements[i];
|
||||
}
|
||||
root <== tree.root;
|
||||
// END tree
|
||||
|
||||
// BEGIN nullifiers
|
||||
component nullifiers_hasher = CalculateNullifier(NULLIFIER_TRAPDOOR_SIZE_IN_BITS, EXTERNAL_NULLIFIER_SIZE_IN_BITS, n_levels);
|
||||
nullifiers_hasher.external_nullifier <== external_nullifier;
|
||||
for (var i = 0; i < NULLIFIER_TRAPDOOR_SIZE_IN_BITS; i++) {
|
||||
nullifiers_hasher.identity_nullifier[i] <== identity_nullifier_bits.out[i];
|
||||
}
|
||||
for (var i = 0; i < n_levels; i++) {
|
||||
nullifiers_hasher.identity_path_index[i] <== identity_path_index[i];
|
||||
}
|
||||
nullifiers_hash <== nullifiers_hasher.nullifiers_hash;
|
||||
// END nullifiers
|
||||
|
||||
// BEGIN verify sig
|
||||
component msg_hasher = MiMCSponge(2, 1);
|
||||
msg_hasher.ins[0] <== external_nullifier;
|
||||
msg_hasher.ins[1] <== signal_hash;
|
||||
msg_hasher.k <== 0;
|
||||
|
||||
component sig_verifier = EdDSAMiMCSpongeVerifier();
|
||||
(1 - fake_zero) ==> sig_verifier.enabled;
|
||||
identity_pk[0] ==> sig_verifier.Ax;
|
||||
identity_pk[1] ==> sig_verifier.Ay;
|
||||
auth_sig_r[0] ==> sig_verifier.R8x;
|
||||
auth_sig_r[1] ==> sig_verifier.R8y;
|
||||
auth_sig_s ==> sig_verifier.S;
|
||||
msg_hasher.outs[0] ==> sig_verifier.M;
|
||||
|
||||
// END verify sig
|
||||
}
|
||||
3
circuits/circom/semaphore.circom
Normal file
3
circuits/circom/semaphore.circom
Normal file
@@ -0,0 +1,3 @@
|
||||
include "./semaphore-base.circom";
|
||||
|
||||
component main = Semaphore(20);
|
||||
29
circuits/jest.config.js
Normal file
29
circuits/jest.config.js
Normal file
@@ -0,0 +1,29 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
transform: {
|
||||
"^.+\\.tsx?$": 'ts-jest'
|
||||
},
|
||||
testPathIgnorePatterns: [
|
||||
"/build/",
|
||||
"/node_modules/",
|
||||
],
|
||||
testRegex: '/__tests__/.*\\.test\\.ts$',
|
||||
moduleFileExtensions: [
|
||||
'ts',
|
||||
'tsx',
|
||||
'js',
|
||||
'jsx',
|
||||
'json',
|
||||
'node'
|
||||
],
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
diagnostics: {
|
||||
// Do not fail on TS compilation errors
|
||||
// https://kulshekhar.github.io/ts-jest/user/config/diagnostics#do-not-fail-on-first-error
|
||||
warnOnly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
testEnvironment: 'node'
|
||||
}
|
||||
8451
circuits/package-lock.json
generated
Normal file
8451
circuits/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
circuits/package.json
Normal file
24
circuits/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "semaphore-circuits",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc",
|
||||
"test-blake2s": "NODE_ENV=local-dev jest --forceExit --testPathPattern Blake2s.test.ts Uint32.test.ts"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.20",
|
||||
"@types/node": "^12.7.7",
|
||||
"circom": "0.0.34",
|
||||
"circomlib": "git+https://github.com/kobigurk/circomlib.git#4284dc1ef984a204db08864f5da530c97f9376ef",
|
||||
"jest": "^24.9.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"snarkjs": "https://github.com/weijiekoh/snarkjs.git#ef8bbbbe5a7d37f59cdb45d3fdf2c1dcf7dd9c7a",
|
||||
"ts-jest": "^24.1.0",
|
||||
"typescript": "^3.7.3",
|
||||
"websnark": "0.0.5"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
58
circuits/scripts/build_snarks.sh
Executable file
58
circuits/scripts/build_snarks.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# semaphorejs - Zero-knowledge signaling on Ethereum
|
||||
# Copyright (C) 2019 Kobi Gurkan <kobigurk@gmail.com>
|
||||
#
|
||||
# This file is part of semaphorejs.
|
||||
#
|
||||
# semaphorejs is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# semaphorejs is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with semaphorejs. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
mkdir -p ../build
|
||||
cd ../build
|
||||
|
||||
if [ -f ./circuit.json ]; then
|
||||
echo "circuit.json already exists. Skipping."
|
||||
else
|
||||
echo 'Generating circuit.json'
|
||||
export NODE_OPTIONS=--max-old-space-size=4096
|
||||
npx circom ../circom/semaphore.circom
|
||||
fi
|
||||
|
||||
if [ -f ./proving_key.json ]; then
|
||||
echo "proving_key.json already exists. Skipping."
|
||||
else
|
||||
echo 'Generating proving_key.json'
|
||||
export NODE_OPTIONS=--max-old-space-size=4096
|
||||
npx snarkjs setup --protocol groth
|
||||
fi
|
||||
|
||||
if [ -f ./proving_key.bin ]; then
|
||||
echo 'proving_key.bin already exists. Skipping.'
|
||||
else
|
||||
echo 'Generating proving_key.bin'
|
||||
export NODE_OPTIONS=--max-old-space-size=4096
|
||||
node ../node_modules/websnark/tools/buildpkey.js -i ./proving_key.json -o ./proving_key.bin
|
||||
fi
|
||||
|
||||
if [ -f ./verifier.sol ]; then
|
||||
echo 'verifier.sol already exists. Skipping.'
|
||||
else
|
||||
echo 'Generating verifier.sol'
|
||||
npx snarkjs generateverifier --vk ./verification_key.json -v ./verifier.sol
|
||||
fi
|
||||
|
||||
# Copy verifier.sol to the contracts/sol directory
|
||||
echo 'Copying verifier.sol to contracts/sol.'
|
||||
cp ./verifier.sol ../../contracts/sol/
|
||||
12
circuits/scripts/checksum_snarks.sh
Executable file
12
circuits/scripts/checksum_snarks.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Used by the CircleCI process to generate a checksum of the snark files so
|
||||
# that it can cache them.
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
mkdir -p ../build
|
||||
cd ../build
|
||||
|
||||
find ../circom -type f -exec md5sum {} \; | sort -k 2 | md5sum > ./.snark_checksum
|
||||
echo 'snark checksum:'
|
||||
cat .snark_checksum
|
||||
35
circuits/scripts/download_snarks.sh
Executable file
35
circuits/scripts/download_snarks.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
CIRCUIT_JSON="https://www.dropbox.com/s/3gzxjibqgb6ke13/circuit.json?dl=1"
|
||||
PROVING_KEY_BIN="https://www.dropbox.com/s/qjlu6v125g7jkcq/proving_key.bin?dl=1"
|
||||
VERIFICATION_KEY_JSON="https://www.dropbox.com/s/rwjwu31c7pzhsth/verification_key.json?dl=1"
|
||||
VERIFIER_SOL="https://www.dropbox.com/s/q5fjzu4zxhc0393/verifier.sol?dl=1"
|
||||
|
||||
CIRCUIT_JSON_PATH="../build/circuit.json"
|
||||
PROVING_KEY_BIN_PATH="../build/proving_key.bin"
|
||||
VERIFICATION_KEY_PATH="../build/verification_key.json"
|
||||
VERIFIER_SOL_PATH="../build/verifier.sol"
|
||||
|
||||
mkdir -p ../build
|
||||
|
||||
if [ ! -f "$CIRCUIT_JSON_PATH" ]; then
|
||||
echo "Downloading circuit.json"
|
||||
wget --quiet $CIRCUIT_JSON -O $CIRCUIT_JSON_PATH
|
||||
fi
|
||||
|
||||
if [ ! -f "$PROVING_KEY_BIN_PATH" ]; then
|
||||
echo "Downloading proving_key.bin"
|
||||
wget --quiet $PROVING_KEY_BIN -O $PROVING_KEY_BIN_PATH
|
||||
fi
|
||||
|
||||
if [ ! -f "$VERIFICATION_KEY_PATH" ]; then
|
||||
echo "Downloading verification_key.json"
|
||||
wget --quiet $VERIFICATION_KEY_JSON -O $VERIFICATION_KEY_PATH
|
||||
fi
|
||||
|
||||
if [ ! -f "$VERIFIER_SOL_PATH" ]; then
|
||||
echo "Downloading verifier.sol"
|
||||
wget --quiet $VERIFIER_SOL -O $VERIFIER_SOL_PATH
|
||||
fi
|
||||
6
circuits/scripts/runTestsInCircleCi.sh
Executable file
6
circuits/scripts/runTestsInCircleCi.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash -xe
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
cd ..
|
||||
|
||||
npm run test-blake2s
|
||||
@@ -1,90 +0,0 @@
|
||||
pragma circom 2.0.0;
|
||||
|
||||
include "../node_modules/circomlib/circuits/poseidon.circom";
|
||||
include "./tree.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() {
|
||||
signal input secret;
|
||||
|
||||
signal output out;
|
||||
|
||||
component poseidon = Poseidon(1);
|
||||
|
||||
poseidon.inputs[0] <== secret;
|
||||
|
||||
out <== poseidon.out;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// nLevels must be < 32.
|
||||
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);
|
||||
@@ -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];
|
||||
}
|
||||
51
circuits/ts/__tests__/blake2s/Blake2s.test.ts
Normal file
51
circuits/ts/__tests__/blake2s/Blake2s.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(1000000)
|
||||
|
||||
import * as path from 'path'
|
||||
import * as snarkjs from 'snarkjs'
|
||||
const bigInt = snarkjs.bigInt
|
||||
import * as crypto from 'crypto'
|
||||
import * as compiler from 'circom'
|
||||
|
||||
describe('Blake2s should match test vectors', () => {
|
||||
test('Should compile mixing_g', async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, 'mixing_g_test.circom'))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log('Vars: '+circuit.nVars)
|
||||
console.log('Constraints: '+circuit.nConstraints)
|
||||
|
||||
})
|
||||
|
||||
test('Should compile blake2s_compression', async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, 'blake2s_compression_test.circom'))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log('Vars: '+circuit.nVars)
|
||||
console.log('Constraints: '+circuit.nConstraints)
|
||||
})
|
||||
|
||||
test('Should run blake2s', async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, 'blake2s_test.circom'))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log('Vars: '+circuit.nVars)
|
||||
console.log('Constraints: '+circuit.nConstraints)
|
||||
|
||||
const bits = '11111111'
|
||||
const inputs = {}
|
||||
for (let i = 0; i < bits.length; i++) {
|
||||
inputs[`in_bits[${i}]`] = bigInt(bits[i])
|
||||
}
|
||||
const witness = circuit.calculateWitness(inputs)
|
||||
|
||||
let coeff = bigInt(1)
|
||||
let result = bigInt(0)
|
||||
for (let i = 0; i < 256; i++) {
|
||||
result = result.add(bigInt(witness[circuit.getSignalIdx(`main.out[${i}]`)].toString()).mul(coeff))
|
||||
coeff = coeff.shl(1)
|
||||
}
|
||||
console.log(`blake2s hash: 0x${result.toString(16)}`)
|
||||
expect(result.toString(16)).toEqual('8a1ef126b4e286703744a80b2f414be700cc93023e7bfc8688b79b54931abd27')
|
||||
})
|
||||
})
|
||||
93
circuits/ts/__tests__/blake2s/Uint32.test.ts
Normal file
93
circuits/ts/__tests__/blake2s/Uint32.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(1000000)
|
||||
|
||||
import * as path from 'path'
|
||||
import * as snarkjs from 'snarkjs'
|
||||
const bigInt = snarkjs.bigInt
|
||||
import * as crypto from 'crypto'
|
||||
import * as compiler from 'circom'
|
||||
|
||||
describe("Uint32 test", () => {
|
||||
test("Should add 5 Uint32s", async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, "uint32_add_test.circom"))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log("Vars: "+circuit.nVars)
|
||||
console.log("Constraints: "+circuit.nConstraints)
|
||||
|
||||
const inputs = {}
|
||||
|
||||
const nums = [4294967295, 4294967294, 4294967293, 4294967292, 4294967291]
|
||||
for (let i = 0; i < nums.length; i++) {
|
||||
let num = bigInt(nums[i])
|
||||
//inputs[`nums_vals[${i}]`] = num
|
||||
for (let j = 0; j < 32; j++) {
|
||||
// this extracts the least significant bit
|
||||
const bit = num.and(bigInt(1))
|
||||
inputs[`nums_bits[${i}][${j}]`] = bit
|
||||
num = num.shr(1)
|
||||
}
|
||||
}
|
||||
const witness = circuit.calculateWitness(inputs)
|
||||
|
||||
let expected_num = nums.reduce((result, current) => {
|
||||
if (result == bigInt(-1)) {
|
||||
result = bigInt(current)
|
||||
} else {
|
||||
result = result.add(bigInt(current))
|
||||
}
|
||||
return result
|
||||
}, bigInt(-1))
|
||||
let expected_bits: number[] = []
|
||||
for (let j = 0; j < 32; j++) {
|
||||
const bit = expected_num.and(bigInt(1))
|
||||
expected_bits.push(bit)
|
||||
expected_num = expected_num.shr(1)
|
||||
}
|
||||
|
||||
for (let i = 0; i < 32; i++) {
|
||||
expect(witness[circuit.getSignalIdx(`main.out_bits[${i}]`)].toString()).toEqual(snarkjs.bigInt(expected_bits[i]).toString())
|
||||
}
|
||||
})
|
||||
|
||||
test("Should xor 2 Uint32s", async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, "uint32_xor_test.circom"))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log("Vars: "+circuit.nVars)
|
||||
console.log("Constraints: "+circuit.nConstraints)
|
||||
|
||||
const inputs = {}
|
||||
|
||||
const nums = [24959295, 4594067494]
|
||||
for (let i = 0; i < nums.length; i++) {
|
||||
let num = bigInt(nums[i])
|
||||
//inputs[`nums_vals[${i}]`] = num
|
||||
for (let j = 0; j < 32; j++) {
|
||||
// this extracts the least significant bit
|
||||
const bit = num.and(bigInt(1))
|
||||
let var_name
|
||||
if (i == 0) {
|
||||
var_name = 'a'
|
||||
} else {
|
||||
var_name = 'b'
|
||||
}
|
||||
inputs[`${var_name}_bits[${j}]`] = bit
|
||||
num = num.shr(1)
|
||||
}
|
||||
}
|
||||
const witness = circuit.calculateWitness(inputs)
|
||||
|
||||
let expected_num = bigInt(nums[0] ^ nums[1])
|
||||
let expected_bits: number[] = []
|
||||
for (let j = 0; j < 32; j++) {
|
||||
const bit = expected_num.and(bigInt(1))
|
||||
expected_bits.push(bit)
|
||||
expected_num = expected_num.shr(1)
|
||||
}
|
||||
|
||||
for (let i = 0; i < 32; i++) {
|
||||
expect(witness[circuit.getSignalIdx(`main.out_bits[${i}]`)].toString()).toEqual(snarkjs.bigInt(expected_bits[i]).toString())
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
include "../../../circom/blake2s/blake2s.circom";
|
||||
|
||||
component main = Blake2sCompression(5, 0);
|
||||
3
circuits/ts/__tests__/blake2s/blake2s_test.circom
Normal file
3
circuits/ts/__tests__/blake2s/blake2s_test.circom
Normal file
@@ -0,0 +1,3 @@
|
||||
include "../../../circom/blake2s/blake2s.circom";
|
||||
|
||||
component main = Blake2s(8, 0);
|
||||
21
circuits/ts/__tests__/blake2s/mixing_g_test.circom
Normal file
21
circuits/ts/__tests__/blake2s/mixing_g_test.circom
Normal file
@@ -0,0 +1,21 @@
|
||||
include "../../../circom/blake2s/blake2s.circom";
|
||||
|
||||
template MixingGTester(a, b, c, d) {
|
||||
signal input in_v[16][32];
|
||||
signal input x[32];
|
||||
signal input y[32];
|
||||
|
||||
signal out_v[16][32];
|
||||
|
||||
component mixing_g = MixingG(a, b, c, d);
|
||||
for (var i = 0; i < 32; i++) {
|
||||
mixing_g.x[i] <== x[i];
|
||||
mixing_g.y[i] <== y[i];
|
||||
for (var j = 0; j < 16; j++) {
|
||||
mixing_g.in_v[j][i] <== in_v[j][i];
|
||||
mixing_g.out_v[j][i] <== out_v[j][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component main = MixingGTester(1, 2, 3, 4);
|
||||
3
circuits/ts/__tests__/blake2s/uint32_add_test.circom
Normal file
3
circuits/ts/__tests__/blake2s/uint32_add_test.circom
Normal file
@@ -0,0 +1,3 @@
|
||||
include "../../../circom/blake2s/uint32.circom";
|
||||
|
||||
component main = Uint32Add(5);
|
||||
3
circuits/ts/__tests__/blake2s/uint32_xor_test.circom
Normal file
3
circuits/ts/__tests__/blake2s/uint32_xor_test.circom
Normal file
@@ -0,0 +1,3 @@
|
||||
include "../../../circom/blake2s/uint32.circom";
|
||||
|
||||
component main = Uint32Xor();
|
||||
11
circuits/ts/bin2hex.ts
Normal file
11
circuits/ts/bin2hex.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
const bin = process.argv[2]
|
||||
|
||||
const buf1 = Buffer.alloc(bin.length/8)
|
||||
for (let i = 0; i < buf1.length; i++) {
|
||||
let t = 1
|
||||
for (let j = 0; j < 8; j++) {
|
||||
buf1[i] |= bin[8*i + j] == '1' ? t : 0
|
||||
t *= 2
|
||||
}
|
||||
}
|
||||
console.log(buf1.toString('hex'))
|
||||
10
circuits/ts/hex2bin.ts
Normal file
10
circuits/ts/hex2bin.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
const buf = Buffer.from(process.argv[2], 'hex')
|
||||
let s = ''
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
let t = 1
|
||||
for (let j = 0; j < 8; j++) {
|
||||
s += ((buf[i] & t) == t) ? '1' : '0'
|
||||
t *= 2
|
||||
}
|
||||
}
|
||||
console.log(s)
|
||||
0
circuits/ts/index.ts
Normal file
0
circuits/ts/index.ts
Normal file
9
circuits/tsconfig.json
Normal file
9
circuits/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": [
|
||||
"./ts"
|
||||
]
|
||||
}
|
||||
7
config/local-dev.yaml
Normal file
7
config/local-dev.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
env: 'local-dev'
|
||||
|
||||
chain:
|
||||
url: "http://localhost:8545"
|
||||
chainId: 1234
|
||||
mnemonic: "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"
|
||||
89
config/package-lock.json
generated
Normal file
89
config/package-lock.json
generated
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "semaphore-config",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "12.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.3.tgz",
|
||||
"integrity": "sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw==",
|
||||
"dev": true
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/config/-/config-3.2.4.tgz",
|
||||
"integrity": "sha512-H1XIGfnU1EAkfjSLn9ZvYDRx9lOezDViuzLDgiJ/lMeqjYe3q6iQfpcLt2NInckJgpAeekbNhQkmnnbdEDs9rw==",
|
||||
"requires": {
|
||||
"json5": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||
},
|
||||
"path": {
|
||||
"version": "0.12.7",
|
||||
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
|
||||
"integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=",
|
||||
"requires": {
|
||||
"process": "^0.11.1",
|
||||
"util": "^0.10.3"
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"util": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
||||
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
|
||||
"requires": {
|
||||
"inherits": "2.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
config/package.json
Normal file
17
config/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "semaphore-config",
|
||||
"version": "0.0.1",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"config": "^3.1.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"path": "^0.12.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.0.10"
|
||||
}
|
||||
}
|
||||
15
config/ts/index.ts
Normal file
15
config/ts/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as path from 'path'
|
||||
|
||||
// set NODE_CONFIG_DIR
|
||||
|
||||
if (!process.env.hasOwnProperty('NODE_CONFIG_DIR')) {
|
||||
process.env.NODE_CONFIG_DIR = path.join(__dirname, '../')
|
||||
}
|
||||
|
||||
if (!process.env.hasOwnProperty('NODE_ENV')) {
|
||||
process.env.NODE_ENV = 'local-dev'
|
||||
}
|
||||
|
||||
const config = require('config')
|
||||
|
||||
export { config }
|
||||
9
config/tsconfig.json
Normal file
9
config/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": [
|
||||
"./ts"
|
||||
]
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<p align="center">
|
||||
<h1 align="center">
|
||||
Semaphore contracts
|
||||
</h1>
|
||||
<p align="center">Semaphore contracts to manage groups and broadcast anonymous signals.</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/semaphore-protocol">
|
||||
<img src="https://img.shields.io/badge/project-semaphore-blue.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
|
||||
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmjs.com/package/@semaphore-protocol/contracts">
|
||||
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/contracts?style=flat-square" />
|
||||
</a>
|
||||
<a href="https://npmjs.org/package/@semaphore-protocol/contracts">
|
||||
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/contracts.svg?style=flat-square" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<h4>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
|
||||
👥 Contributing
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
|
||||
🤝 Code of conduct
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
|
||||
🔎 Issues
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://discord.gg/6mSdGHnstH">
|
||||
🗣️ Chat & Support
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
To learn more about contracts visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org/docs/technical-reference/contracts).
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Install
|
||||
|
||||
### npm or yarn
|
||||
|
||||
Install the `@semaphore-protocol/contracts` package with npm:
|
||||
|
||||
```bash
|
||||
npm i @semaphore-protocol/contracts
|
||||
```
|
||||
|
||||
or yarn:
|
||||
|
||||
```bash
|
||||
yarn add @semaphore-protocol/contracts
|
||||
```
|
||||
@@ -1,175 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "./interfaces/ISemaphore.sol";
|
||||
import "./interfaces/IVerifier.sol";
|
||||
import "./base/SemaphoreCore.sol";
|
||||
import "./base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore
|
||||
contract Semaphore is ISemaphore, SemaphoreCore, SemaphoreGroups {
|
||||
/// @dev Gets a tree depth and returns its verifier address.
|
||||
mapping(uint256 => IVerifier) public verifiers;
|
||||
|
||||
/// @dev Gets a group id and returns the group admin address.
|
||||
mapping(uint256 => address) public groupAdmins;
|
||||
|
||||
/// @dev Gets a group id and returns data to check if a Merkle root is expired.
|
||||
mapping(uint256 => MerkleTreeExpiry) public merkleTreeExpiries;
|
||||
|
||||
/// @dev Checks if the group admin is the transaction sender.
|
||||
/// @param groupId: Id of the group.
|
||||
modifier onlyGroupAdmin(uint256 groupId) {
|
||||
if (groupAdmins[groupId] != _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 (address(verifiers[merkleTreeDepth]) == address(0)) {
|
||||
revert Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs.
|
||||
/// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth).
|
||||
constructor(Verifier[] memory _verifiers) {
|
||||
for (uint8 i = 0; i < _verifiers.length; ) {
|
||||
verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress);
|
||||
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-createGroup}.
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeDepth,
|
||||
uint256 zeroValue,
|
||||
address admin
|
||||
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
|
||||
_createGroup(groupId, merkleTreeDepth, zeroValue);
|
||||
|
||||
groupAdmins[groupId] = admin;
|
||||
merkleTreeExpiries[groupId].rootDuration = 1 hours;
|
||||
|
||||
emit GroupAdminUpdated(groupId, address(0), admin);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-createGroup}.
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeDepth,
|
||||
uint256 zeroValue,
|
||||
address admin,
|
||||
uint256 merkleTreeRootDuration
|
||||
) external override onlySupportedMerkleTreeDepth(merkleTreeDepth) {
|
||||
_createGroup(groupId, merkleTreeDepth, zeroValue);
|
||||
|
||||
groupAdmins[groupId] = admin;
|
||||
merkleTreeExpiries[groupId].rootDuration = merkleTreeRootDuration;
|
||||
|
||||
emit GroupAdminUpdated(groupId, address(0), admin);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-updateGroupAdmin}.
|
||||
function updateGroupAdmin(uint256 groupId, address newAdmin) external override onlyGroupAdmin(groupId) {
|
||||
groupAdmins[groupId] = newAdmin;
|
||||
|
||||
emit GroupAdminUpdated(groupId, _msgSender(), newAdmin);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-addMember}.
|
||||
function addMember(uint256 groupId, uint256 identityCommitment) external override onlyGroupAdmin(groupId) {
|
||||
_addMember(groupId, identityCommitment);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot] = block.timestamp;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-addMembers}.
|
||||
function addMembers(uint256 groupId, uint256[] calldata identityCommitments)
|
||||
external
|
||||
override
|
||||
onlyGroupAdmin(groupId)
|
||||
{
|
||||
for (uint8 i = 0; i < identityCommitments.length; ) {
|
||||
_addMember(groupId, identityCommitments[i]);
|
||||
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot] = block.timestamp;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-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);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-removeMember}.
|
||||
function removeMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) external override onlyGroupAdmin(groupId) {
|
||||
_removeMember(groupId, identityCommitment, proofSiblings, proofPathIndices);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphore-verifyProof}.
|
||||
function verifyProof(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeRoot,
|
||||
bytes32 signal,
|
||||
uint256 nullifierHash,
|
||||
uint256 externalNullifier,
|
||||
uint256[8] calldata proof
|
||||
) external override {
|
||||
uint256 currentMerkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
|
||||
if (currentMerkleTreeRoot == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
if (merkleTreeRoot != currentMerkleTreeRoot) {
|
||||
uint256 rootCreationDate = merkleTreeExpiries[groupId].rootCreationDates[merkleTreeRoot];
|
||||
uint256 rootDuration = merkleTreeExpiries[groupId].rootDuration;
|
||||
|
||||
if (rootCreationDate == 0) {
|
||||
revert Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
|
||||
}
|
||||
|
||||
if (block.timestamp > rootCreationDate + rootDuration) {
|
||||
revert Semaphore__MerkleTreeRootIsExpired();
|
||||
}
|
||||
}
|
||||
|
||||
uint256 merkleTreeDepth = getMerkleTreeDepth(groupId);
|
||||
|
||||
IVerifier verifier = verifiers[merkleTreeDepth];
|
||||
|
||||
_verifyProof(signal, merkleTreeRoot, nullifierHash, externalNullifier, proof, verifier);
|
||||
|
||||
_saveNullifierHash(nullifierHash);
|
||||
|
||||
emit ProofVerified(groupId, signal);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
@@ -1,61 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "../interfaces/ISemaphoreCore.sol";
|
||||
import "../interfaces/IVerifier.sol";
|
||||
|
||||
/// @title Semaphore core contract.
|
||||
/// @notice Minimal code to allow users to signal their endorsement of an arbitrary string.
|
||||
/// @dev The following code verifies that the proof is correct and saves the hash of the
|
||||
/// nullifier to prevent double-signaling. External nullifier and Merkle trees (i.e. groups) must be
|
||||
/// managed externally.
|
||||
contract SemaphoreCore is ISemaphoreCore {
|
||||
/// @dev Gets a nullifier hash and returns true or false.
|
||||
/// It is used to prevent double-signaling.
|
||||
mapping(uint256 => bool) internal nullifierHashes;
|
||||
|
||||
/// @dev Asserts that no nullifier already exists and if the zero-knowledge proof is valid.
|
||||
/// Otherwise it reverts.
|
||||
/// @param signal: Semaphore signal.
|
||||
/// @param root: Root of the Merkle tree.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param externalNullifier: External nullifier.
|
||||
/// @param proof: Zero-knowledge proof.
|
||||
/// @param verifier: Verifier address.
|
||||
function _verifyProof(
|
||||
bytes32 signal,
|
||||
uint256 root,
|
||||
uint256 nullifierHash,
|
||||
uint256 externalNullifier,
|
||||
uint256[8] calldata proof,
|
||||
IVerifier verifier
|
||||
) internal view {
|
||||
if (nullifierHashes[nullifierHash]) {
|
||||
revert Semaphore__YouAreUsingTheSameNillifierTwice();
|
||||
}
|
||||
|
||||
uint256 signalHash = _hashSignal(signal);
|
||||
|
||||
verifier.verifyProof(
|
||||
[proof[0], proof[1]],
|
||||
[[proof[2], proof[3]], [proof[4], proof[5]]],
|
||||
[proof[6], proof[7]],
|
||||
[root, nullifierHash, signalHash, externalNullifier]
|
||||
);
|
||||
}
|
||||
|
||||
/// @dev Stores the nullifier hash to prevent double-signaling.
|
||||
/// Attention! Remember to call it when you verify a proof if you
|
||||
/// need to prevent double-signaling.
|
||||
/// @param nullifierHash: Semaphore nullifier hash.
|
||||
function _saveNullifierHash(uint256 nullifierHash) internal {
|
||||
nullifierHashes[nullifierHash] = true;
|
||||
}
|
||||
|
||||
/// @dev Creates a keccak256 hash of the signal.
|
||||
/// @param signal: Semaphore signal.
|
||||
/// @return Hash of the signal.
|
||||
function _hashSignal(bytes32 signal) private pure returns (uint256) {
|
||||
return uint256(keccak256(abi.encodePacked(signal))) >> 8;
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import {SNARK_SCALAR_FIELD} from "./SemaphoreConstants.sol";
|
||||
import "../interfaces/ISemaphoreGroups.sol";
|
||||
import "@zk-kit/incremental-merkle-tree.sol/IncrementalBinaryTree.sol";
|
||||
import "@openzeppelin/contracts/utils/Context.sol";
|
||||
|
||||
/// @title Semaphore groups contract.
|
||||
/// @dev The following code allows you to create groups, add and remove 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;
|
||||
|
||||
/// @dev Gets a group id and returns the group/tree data.
|
||||
mapping(uint256 => IncrementalTreeData) internal groups;
|
||||
|
||||
/// @dev Creates a new group by initializing the associated tree.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
/// @param zeroValue: Zero value of the tree.
|
||||
function _createGroup(
|
||||
uint256 groupId,
|
||||
uint256 merkleTreeDepth,
|
||||
uint256 zeroValue
|
||||
) internal virtual {
|
||||
if (groupId >= SNARK_SCALAR_FIELD) {
|
||||
revert Semaphore__GroupIdIsNotLessThanSnarkScalarField();
|
||||
}
|
||||
|
||||
if (getMerkleTreeDepth(groupId) != 0) {
|
||||
revert Semaphore__GroupAlreadyExists();
|
||||
}
|
||||
|
||||
groups[groupId].init(merkleTreeDepth, zeroValue);
|
||||
|
||||
emit GroupCreated(groupId, merkleTreeDepth, zeroValue);
|
||||
}
|
||||
|
||||
/// @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();
|
||||
}
|
||||
|
||||
groups[groupId].insert(identityCommitment);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = getNumberOfMerkleTreeLeaves(groupId) - 1;
|
||||
|
||||
emit MemberAdded(groupId, index, identityCommitment, 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 newIdentityCommitment: New identity commitment.
|
||||
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPathIndices: Path of the proof of membership.
|
||||
function _updateMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256 newIdentityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) internal virtual {
|
||||
if (getMerkleTreeRoot(groupId) == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
groups[groupId].update(identityCommitment, newIdentityCommitment, proofSiblings, proofPathIndices);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
|
||||
|
||||
emit MemberUpdated(groupId, index, identityCommitment, 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.
|
||||
function _removeMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) internal virtual {
|
||||
if (getMerkleTreeRoot(groupId) == 0) {
|
||||
revert Semaphore__GroupDoesNotExist();
|
||||
}
|
||||
|
||||
groups[groupId].remove(identityCommitment, proofSiblings, proofPathIndices);
|
||||
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(groupId);
|
||||
uint256 index = proofPathIndicesToMemberIndex(proofPathIndices);
|
||||
|
||||
emit MemberRemoved(groupId, index, identityCommitment, merkleTreeRoot);
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getMerkleTreeRoot}.
|
||||
function getMerkleTreeRoot(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return groups[groupId].root;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getMerkleTreeDepth}.
|
||||
function getMerkleTreeDepth(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return groups[groupId].depth;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreGroups-getNumberOfMerkleTreeLeaves}.
|
||||
function getNumberOfMerkleTreeLeaves(uint256 groupId) public view virtual override returns (uint256) {
|
||||
return groups[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;
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "../interfaces/ISemaphoreVoting.sol";
|
||||
import "../base/SemaphoreCore.sol";
|
||||
import "../base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore voting contract.
|
||||
/// @dev The following code allows you to create polls, add voters and allow them to vote anonymously.
|
||||
contract SemaphoreVoting is ISemaphoreVoting, SemaphoreCore, SemaphoreGroups {
|
||||
/// @dev Gets a tree depth and returns its verifier address.
|
||||
mapping(uint256 => IVerifier) internal verifiers;
|
||||
|
||||
/// @dev Gets a poll id and returns the poll data.
|
||||
mapping(uint256 => Poll) internal polls;
|
||||
|
||||
/// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs.
|
||||
/// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth).
|
||||
constructor(Verifier[] memory _verifiers) {
|
||||
for (uint8 i = 0; i < _verifiers.length; ) {
|
||||
verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress);
|
||||
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @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 See {ISemaphoreVoting-createPoll}.
|
||||
function createPoll(
|
||||
uint256 pollId,
|
||||
address coordinator,
|
||||
uint256 merkleTreeDepth
|
||||
) public override {
|
||||
if (address(verifiers[merkleTreeDepth]) == address(0)) {
|
||||
revert Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
}
|
||||
|
||||
_createGroup(pollId, merkleTreeDepth, 0);
|
||||
|
||||
Poll memory poll;
|
||||
|
||||
poll.coordinator = coordinator;
|
||||
|
||||
polls[pollId] = poll;
|
||||
|
||||
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(
|
||||
bytes32 vote,
|
||||
uint256 nullifierHash,
|
||||
uint256 pollId,
|
||||
uint256[8] calldata proof
|
||||
) public override onlyCoordinator(pollId) {
|
||||
Poll memory poll = polls[pollId];
|
||||
|
||||
if (poll.state != PollState.Ongoing) {
|
||||
revert Semaphore__PollIsNotOngoing();
|
||||
}
|
||||
|
||||
uint256 merkleTreeDepth = getMerkleTreeDepth(pollId);
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(pollId);
|
||||
|
||||
IVerifier verifier = verifiers[merkleTreeDepth];
|
||||
|
||||
_verifyProof(vote, merkleTreeRoot, nullifierHash, pollId, proof, verifier);
|
||||
|
||||
// Prevent double-voting (nullifierHash = hash(pollId + identityNullifier)).
|
||||
_saveNullifierHash(nullifierHash);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
import "../interfaces/ISemaphoreWhistleblowing.sol";
|
||||
import "../base/SemaphoreCore.sol";
|
||||
import "../base/SemaphoreGroups.sol";
|
||||
|
||||
/// @title Semaphore whistleblowing contract.
|
||||
/// @dev The following code allows you to create entities for whistleblowers (e.g. non-profit
|
||||
/// organization, newspaper) and to allow them to publish news leaks anonymously.
|
||||
/// Leaks can be IPFS hashes, permanent links or other kinds of reference.
|
||||
contract SemaphoreWhistleblowing is ISemaphoreWhistleblowing, SemaphoreCore, SemaphoreGroups {
|
||||
/// @dev Gets a tree depth and returns its verifier address.
|
||||
mapping(uint256 => IVerifier) internal verifiers;
|
||||
|
||||
/// @dev Gets an editor address and return their entity.
|
||||
mapping(address => uint256) private entities;
|
||||
|
||||
/// @dev Initializes the Semaphore verifiers used to verify the user's ZK proofs.
|
||||
/// @param _verifiers: List of Semaphore verifiers (address and related Merkle tree depth).
|
||||
constructor(Verifier[] memory _verifiers) {
|
||||
for (uint8 i = 0; i < _verifiers.length; ) {
|
||||
verifiers[_verifiers[i].merkleTreeDepth] = IVerifier(_verifiers[i].contractAddress);
|
||||
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Checks if the editor is the transaction sender.
|
||||
/// @param entityId: Id of the entity.
|
||||
modifier onlyEditor(uint256 entityId) {
|
||||
if (entityId != entities[_msgSender()]) {
|
||||
revert Semaphore__CallerIsNotTheEditor();
|
||||
}
|
||||
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev See {ISemaphoreWhistleblowing-createEntity}.
|
||||
function createEntity(
|
||||
uint256 entityId,
|
||||
address editor,
|
||||
uint256 merkleTreeDepth
|
||||
) public override {
|
||||
if (address(verifiers[merkleTreeDepth]) == address(0)) {
|
||||
revert Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
}
|
||||
|
||||
_createGroup(entityId, merkleTreeDepth, 0);
|
||||
|
||||
entities[editor] = entityId;
|
||||
|
||||
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(
|
||||
bytes32 leak,
|
||||
uint256 nullifierHash,
|
||||
uint256 entityId,
|
||||
uint256[8] calldata proof
|
||||
) public override onlyEditor(entityId) {
|
||||
uint256 merkleTreeDepth = getMerkleTreeDepth(entityId);
|
||||
uint256 merkleTreeRoot = getMerkleTreeRoot(entityId);
|
||||
|
||||
IVerifier verifier = verifiers[merkleTreeDepth];
|
||||
|
||||
_verifyProof(leak, merkleTreeRoot, nullifierHash, entityId, proof, verifier);
|
||||
|
||||
emit LeakPublished(entityId, leak);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title Semaphore interface.
|
||||
/// @dev Interface of a Semaphore contract.
|
||||
interface ISemaphore {
|
||||
error Semaphore__CallerIsNotTheGroupAdmin();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
error Semaphore__MerkleTreeRootIsExpired();
|
||||
error Semaphore__MerkleTreeRootIsNotPartOfTheGroup();
|
||||
|
||||
struct Verifier {
|
||||
address contractAddress;
|
||||
uint256 merkleTreeDepth;
|
||||
}
|
||||
|
||||
/// It defines all the parameters needed to check whether a
|
||||
/// zero-knowledge proof generated with a certain Merkle tree is still valid.
|
||||
struct MerkleTreeExpiry {
|
||||
uint256 rootDuration;
|
||||
mapping(uint256 => uint256) rootCreationDates;
|
||||
}
|
||||
|
||||
/// @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 a Semaphore proof is verified.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param signal: Semaphore signal.
|
||||
event ProofVerified(uint256 indexed groupId, bytes32 signal);
|
||||
|
||||
/// @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,
|
||||
bytes32 signal,
|
||||
uint256 nullifierHash,
|
||||
uint256 externalNullifier,
|
||||
uint256[8] calldata proof
|
||||
) 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 zeroValue: Zero value of the tree.
|
||||
/// @param admin: Admin of the group.
|
||||
function createGroup(
|
||||
uint256 groupId,
|
||||
uint256 depth,
|
||||
uint256 zeroValue,
|
||||
address admin
|
||||
) 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 zeroValue: Zero value 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,
|
||||
uint256 zeroValue,
|
||||
address admin,
|
||||
uint256 merkleTreeRootDuration
|
||||
) external;
|
||||
|
||||
/// @dev Updates the group admin.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param newAdmin: New admin of the group.
|
||||
function updateGroupAdmin(uint256 groupId, address newAdmin) external;
|
||||
|
||||
/// @dev Adds a new member to an existing group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param identityCommitment: New identity commitment.
|
||||
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.
|
||||
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.
|
||||
function updateMember(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256 newIdentityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) 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.
|
||||
/// @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(
|
||||
uint256 groupId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) external;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreCore interface.
|
||||
/// @dev Interface of SemaphoreCore contract.
|
||||
interface ISemaphoreCore {
|
||||
error Semaphore__YouAreUsingTheSameNillifierTwice();
|
||||
|
||||
/// @notice Emitted when a proof is verified correctly and a new nullifier hash is added.
|
||||
/// @param nullifierHash: Hash of external and identity nullifiers.
|
||||
event NullifierHashAdded(uint256 nullifierHash);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreGroups interface.
|
||||
/// @dev Interface of a SemaphoreGroups contract.
|
||||
interface ISemaphoreGroups {
|
||||
error Semaphore__GroupDoesNotExist();
|
||||
error Semaphore__GroupAlreadyExists();
|
||||
error Semaphore__GroupIdIsNotLessThanSnarkScalarField();
|
||||
|
||||
/// @dev Emitted when a new group is created.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
/// @param zeroValue: Zero value of the tree.
|
||||
event GroupCreated(uint256 indexed groupId, uint256 merkleTreeDepth, uint256 zeroValue);
|
||||
|
||||
/// @dev Emitted when a new identity commitment is added.
|
||||
/// @param groupId: Group id of the group.
|
||||
/// @param index: Identity commitment index.
|
||||
/// @param identityCommitment: New identity commitment.
|
||||
/// @param merkleTreeRoot: New root hash of the tree.
|
||||
event MemberAdded(uint256 indexed groupId, uint256 index, uint256 identityCommitment, uint256 merkleTreeRoot);
|
||||
|
||||
/// @dev Emitted when an identity commitment is updated.
|
||||
/// @param groupId: Group id of the group.
|
||||
/// @param index: Identity commitment index.
|
||||
/// @param identityCommitment: Existing identity commitment to be updated.
|
||||
/// @param newIdentityCommitment: New identity commitment.
|
||||
/// @param merkleTreeRoot: New root hash of the tree.
|
||||
event MemberUpdated(
|
||||
uint256 indexed groupId,
|
||||
uint256 index,
|
||||
uint256 identityCommitment,
|
||||
uint256 newIdentityCommitment,
|
||||
uint256 merkleTreeRoot
|
||||
);
|
||||
|
||||
/// @dev Emitted when a new identity commitment is removed.
|
||||
/// @param groupId: Group id of the group.
|
||||
/// @param index: Identity commitment index.
|
||||
/// @param identityCommitment: Existing identity commitment to be removed.
|
||||
/// @param merkleTreeRoot: New root hash of the tree.
|
||||
event MemberRemoved(uint256 indexed groupId, uint256 index, uint256 identityCommitment, uint256 merkleTreeRoot);
|
||||
|
||||
/// @dev Returns the last root hash of a group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @return Root hash of the group.
|
||||
function getMerkleTreeRoot(uint256 groupId) external view returns (uint256);
|
||||
|
||||
/// @dev Returns the depth of the tree of a group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @return Depth of the group tree.
|
||||
function getMerkleTreeDepth(uint256 groupId) external view returns (uint256);
|
||||
|
||||
/// @dev Returns the number of tree leaves of a group.
|
||||
/// @param groupId: Id of the group.
|
||||
/// @return Number of tree leaves.
|
||||
function getNumberOfMerkleTreeLeaves(uint256 groupId) external view returns (uint256);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreNullifiers interface.
|
||||
/// @dev Interface of SemaphoreNullifiers contract.
|
||||
interface ISemaphoreNullifiers {
|
||||
/// @dev Emitted when a external nullifier is added.
|
||||
/// @param externalNullifier: External Semaphore nullifier.
|
||||
event ExternalNullifierAdded(uint256 externalNullifier);
|
||||
|
||||
/// @dev Emitted when a external nullifier is removed.
|
||||
/// @param externalNullifier: External Semaphore nullifier.
|
||||
event ExternalNullifierRemoved(uint256 externalNullifier);
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreVoting interface.
|
||||
/// @dev Interface of SemaphoreVoting contract.
|
||||
interface ISemaphoreVoting {
|
||||
error Semaphore__CallerIsNotThePollCoordinator();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
error Semaphore__PollHasAlreadyBeenStarted();
|
||||
error Semaphore__PollIsNotOngoing();
|
||||
|
||||
enum PollState {
|
||||
Created,
|
||||
Ongoing,
|
||||
Ended
|
||||
}
|
||||
|
||||
struct Verifier {
|
||||
address contractAddress;
|
||||
uint256 merkleTreeDepth;
|
||||
}
|
||||
|
||||
struct Poll {
|
||||
address coordinator;
|
||||
PollState state;
|
||||
}
|
||||
|
||||
/// @dev Emitted when a new poll is created.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param coordinator: Coordinator of the poll.
|
||||
event PollCreated(uint256 pollId, address indexed coordinator);
|
||||
|
||||
/// @dev Emitted when a poll is started.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param coordinator: Coordinator of the poll.
|
||||
/// @param encryptionKey: Key to encrypt the poll votes.
|
||||
event PollStarted(uint256 pollId, address indexed coordinator, uint256 encryptionKey);
|
||||
|
||||
/// @dev Emitted when a user votes on a poll.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param vote: User encrypted vote.
|
||||
event VoteAdded(uint256 indexed pollId, bytes32 vote);
|
||||
|
||||
/// @dev Emitted when a poll is ended.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param coordinator: Coordinator of the poll.
|
||||
/// @param decryptionKey: Key to decrypt the poll votes.
|
||||
event PollEnded(uint256 pollId, address indexed coordinator, uint256 decryptionKey);
|
||||
|
||||
/// @dev Creates a poll and the associated Merkle tree/group.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param coordinator: Coordinator of the poll.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
function createPoll(
|
||||
uint256 pollId,
|
||||
address coordinator,
|
||||
uint256 merkleTreeDepth
|
||||
) external;
|
||||
|
||||
/// @dev Adds a voter to a poll.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param identityCommitment: Identity commitment of the group member.
|
||||
function addVoter(uint256 pollId, uint256 identityCommitment) external;
|
||||
|
||||
/// @dev Starts a pull and publishes the key to encrypt the votes.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param encryptionKey: Key to encrypt poll votes.
|
||||
function startPoll(uint256 pollId, uint256 encryptionKey) external;
|
||||
|
||||
/// @dev Casts an anonymous vote in a poll.
|
||||
/// @param vote: Encrypted vote.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param proof: Private zk-proof parameters.
|
||||
function castVote(
|
||||
bytes32 vote,
|
||||
uint256 nullifierHash,
|
||||
uint256 pollId,
|
||||
uint256[8] calldata proof
|
||||
) external;
|
||||
|
||||
/// @dev Ends a pull and publishes the key to decrypt the votes.
|
||||
/// @param pollId: Id of the poll.
|
||||
/// @param decryptionKey: Key to decrypt poll votes.
|
||||
function endPoll(uint256 pollId, uint256 decryptionKey) external;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title SemaphoreWhistleblowing interface.
|
||||
/// @dev Interface of SemaphoreWhistleblowing contract.
|
||||
interface ISemaphoreWhistleblowing {
|
||||
error Semaphore__CallerIsNotTheEditor();
|
||||
error Semaphore__MerkleTreeDepthIsNotSupported();
|
||||
|
||||
struct Verifier {
|
||||
address contractAddress;
|
||||
uint256 merkleTreeDepth;
|
||||
}
|
||||
|
||||
/// @dev Emitted when a new entity is created.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param editor: Editor of the entity.
|
||||
event EntityCreated(uint256 entityId, address indexed editor);
|
||||
|
||||
/// @dev Emitted when a whistleblower publish a new leak.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param leak: News leak.
|
||||
event LeakPublished(uint256 indexed entityId, bytes32 leak);
|
||||
|
||||
/// @dev Creates an entity and the associated Merkle tree/group.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param editor: Editor of the entity.
|
||||
/// @param merkleTreeDepth: Depth of the tree.
|
||||
function createEntity(
|
||||
uint256 entityId,
|
||||
address editor,
|
||||
uint256 merkleTreeDepth
|
||||
) external;
|
||||
|
||||
/// @dev Adds a whistleblower to an entity.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param identityCommitment: Identity commitment of the group member.
|
||||
function addWhistleblower(uint256 entityId, uint256 identityCommitment) external;
|
||||
|
||||
/// @dev Removes a whistleblower from an entity.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param identityCommitment: Identity commitment of the group member.
|
||||
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
|
||||
/// @param proofPathIndices: Path of the proof of membership.
|
||||
function removeWhistleblower(
|
||||
uint256 entityId,
|
||||
uint256 identityCommitment,
|
||||
uint256[] calldata proofSiblings,
|
||||
uint8[] calldata proofPathIndices
|
||||
) external;
|
||||
|
||||
/// @dev Allows whistleblowers to publish leaks anonymously.
|
||||
/// @param leak: News leak.
|
||||
/// @param nullifierHash: Nullifier hash.
|
||||
/// @param entityId: Id of the entity.
|
||||
/// @param proof: Private zk-proof parameters.
|
||||
function publishLeak(
|
||||
bytes32 leak,
|
||||
uint256 nullifierHash,
|
||||
uint256 entityId,
|
||||
uint256[8] calldata proof
|
||||
) external;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.4;
|
||||
|
||||
/// @title Verifier interface.
|
||||
/// @dev Interface of Verifier contract.
|
||||
interface IVerifier {
|
||||
function verifyProof(
|
||||
uint256[2] memory a,
|
||||
uint256[2][2] memory b,
|
||||
uint256[2] memory c,
|
||||
uint256[4] memory input
|
||||
) external view;
|
||||
}
|
||||
32
contracts/jest.config.js
Normal file
32
contracts/jest.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
transform: {
|
||||
"^.+\\.tsx?$": 'ts-jest'
|
||||
},
|
||||
testPathIgnorePatterns: [
|
||||
"/build/",
|
||||
"/node_modules/",
|
||||
],
|
||||
testRegex: '/__tests__/.*\\.test\\.ts$',
|
||||
moduleFileExtensions: [
|
||||
'ts',
|
||||
'tsx',
|
||||
'js',
|
||||
'jsx',
|
||||
'json',
|
||||
'node'
|
||||
],
|
||||
moduleNameMapper: {
|
||||
"^@semaphore-contracts(.*)$": "<rootDir>./$1",
|
||||
},
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
diagnostics: {
|
||||
// Do not fail on TS compilation errors
|
||||
// https://kulshekhar.github.io/ts-jest/user/config/diagnostics#do-not-fail-on-first-error
|
||||
warnOnly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
testEnvironment: 'node'
|
||||
}
|
||||
1
contracts/package-lock.json.REMOVED.git-id
Normal file
1
contracts/package-lock.json.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
a22dd75fdaddd98d93ccf3467f78af832dc94864
|
||||
@@ -1,38 +1,39 @@
|
||||
{
|
||||
"name": "@semaphore-protocol/contracts",
|
||||
"version": "2.2.0",
|
||||
"description": "Semaphore contracts to manage groups and broadcast anonymous signals.",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"**/*.sol"
|
||||
],
|
||||
"keywords": [
|
||||
"blockchain",
|
||||
"ethereum",
|
||||
"hardhat",
|
||||
"smart-contracts",
|
||||
"semaphore",
|
||||
"identity",
|
||||
"solidity",
|
||||
"zero-knowledge",
|
||||
"zk-snarks",
|
||||
"zero-knowledge-proofs",
|
||||
"circom",
|
||||
"proof-of-membership"
|
||||
],
|
||||
"homepage": "https://github.com/semaphore-protocol/semaphore.git#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/semaphore-protocol/semaphore.git.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"name": "semaphore-contracts",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"watch": "tsc --watch",
|
||||
"ganache": "./scripts/runGanache.sh",
|
||||
"compileSol": "./scripts/compileSol.sh",
|
||||
"test-semaphore": "NODE_ENV=local-dev jest --forceExit --testPathPattern Semaphore.test.ts",
|
||||
"test-semaphore-debug": "NODE_ENV=local-dev node --inspect-brk ./node_modules/jest/bin/jest.js --testPathPattern Semaphore.test.ts",
|
||||
"test-verifier": "NODE_ENV=local-dev jest --forceExit --testPathPattern FixedVerifier.test.ts",
|
||||
"test-verifier-debug": "NODE_ENV=local-dev node --inspect-brk ./node_modules/jest/bin/jest.js --testPathPattern FixedVerifier.test.ts",
|
||||
"test-mt": "NODE_ENV=local-dev jest --forceExit --testPathPattern IncrementalMerkleTree.test.ts",
|
||||
"test-mt-debug": "NODE_ENV=local-dev node --inspect-brk ./node_modules/jest/bin/jest.js --testPathPattern IncrementalMerkleTree.test.ts",
|
||||
"build": "tsc"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"@semaphore-contracts": "."
|
||||
},
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "4.4.2",
|
||||
"@zk-kit/incremental-merkle-tree.sol": "1.3.0"
|
||||
"semaphore-config": "0.0.1",
|
||||
"ethers": "4.0.38",
|
||||
"circomlib": "https://github.com/kobigurk/circomlib.git#347822604996bf25f659f96ee0f02810a1f71bb0",
|
||||
"ganache-cli": "^6.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.20",
|
||||
"@types/node": "^12.7.7",
|
||||
"etherlime": "^2.2.4",
|
||||
"etherlime-lib": "^1.1.5",
|
||||
"jest": "^24.9.0",
|
||||
"libsemaphore": "^1.0.14",
|
||||
"semaphore-merkle-tree": "^1.0.12",
|
||||
"module-alias": "^2.2.2",
|
||||
"truffle-artifactor": "^4.0.30",
|
||||
"ts-jest": "^24.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
19
contracts/scripts/compileSol.sh
Executable file
19
contracts/scripts/compileSol.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo 'Building contracts'
|
||||
|
||||
# Delete old files
|
||||
rm -rf ./compiled/*
|
||||
|
||||
mkdir -p ./compiled/abis
|
||||
|
||||
# Copy the Semaphore contracts from the submodule into solidity/
|
||||
|
||||
npx etherlime compile --solcVersion=native --buildDirectory=compiled --workingDirectory=sol --exportAbi
|
||||
|
||||
# Build the MiMC contract from bytecode
|
||||
node build/buildMiMC.js
|
||||
23
contracts/scripts/runGanache.sh
Executable file
23
contracts/scripts/runGanache.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This mnemonic seed is well-known to the public. If you transfer any ETH to
|
||||
# addreses derived from it, expect it to be swept away.
|
||||
|
||||
# Etherlime's ganache command works differently from ganache-cli. It
|
||||
# concatenates `--count minus 10` new accounts generated from `--mnemonic`. The
|
||||
# first 10 are predefined.
|
||||
npx etherlime ganache --mnemonic "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" --gasLimit=8800000 count=10 --networkId 1234
|
||||
|
||||
#npx ganache-cli -a 10 -m='candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' --gasLimit=8800000 --port 8545 -i 1234
|
||||
|
||||
# ETH accounts from the 'candy maple...' mnemonic
|
||||
#0: 0x627306090abab3a6e1400e9345bc60c78a8bef57
|
||||
#1: 0xf17f52151ebef6c7334fad080c5704d77216b732
|
||||
#2: 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
|
||||
#3: 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
|
||||
#4: 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
|
||||
#5: 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
|
||||
#6: 0x2191ef87e392377ec08e7c08eb105ef5448eced5
|
||||
#7: 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
|
||||
#8: 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
|
||||
#9: 0x5aeda56215b167893e80b4fe645ba6d5bab767de
|
||||
10
contracts/scripts/runTestsInCircleCi.sh
Executable file
10
contracts/scripts/runTestsInCircleCi.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash -xe
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
cd ..
|
||||
|
||||
npm run ganache &
|
||||
sleep 3 &&
|
||||
npm run test-semaphore &&
|
||||
sleep 1 &&
|
||||
npm run test-mt
|
||||
180
contracts/sol/IncrementalMerkleTree.sol
Normal file
180
contracts/sol/IncrementalMerkleTree.sol
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import { SnarkConstants } from "./SnarkConstants.sol";
|
||||
import { MiMC } from "./MiMC.sol";
|
||||
|
||||
contract IncrementalMerkleTree is SnarkConstants {
|
||||
// The maximum tree depth
|
||||
uint8 internal constant MAX_DEPTH = 32;
|
||||
|
||||
// The tree depth
|
||||
uint8 internal treeLevels;
|
||||
|
||||
// The number of inserted leaves
|
||||
uint256 internal nextLeafIndex = 0;
|
||||
|
||||
// The Merkle root
|
||||
uint256 public root;
|
||||
|
||||
// The Merkle path to the leftmost leaf upon initialisation. It *should
|
||||
// not* be modified after it has been set by the `initMerkleTree` function.
|
||||
// Caching these values is essential to efficient appends.
|
||||
uint256[MAX_DEPTH] internal zeros;
|
||||
|
||||
// Allows you to compute the path to the element (but it's not the path to
|
||||
// the elements). Caching these values is essential to efficient appends.
|
||||
uint256[MAX_DEPTH] internal filledSubtrees;
|
||||
|
||||
// Whether the contract has already seen a particular Merkle tree root
|
||||
mapping (uint256 => bool) public rootHistory;
|
||||
|
||||
event LeafInsertion(uint256 indexed leaf, uint256 indexed leafIndex);
|
||||
|
||||
/*
|
||||
* Stores the Merkle root and intermediate values (the Merkle path to the
|
||||
* the first leaf) assuming that all leaves are set to _zeroValue.
|
||||
* @param _treeLevels The number of levels of the tree
|
||||
* @param _zeroValue The value to set for every leaf. Ideally, this should
|
||||
* be a nothing-up-my-sleeve value, so that nobody can
|
||||
* say that the deployer knows the preimage of an empty
|
||||
* leaf.
|
||||
*/
|
||||
constructor(uint8 _treeLevels, uint256 _zeroValue) internal {
|
||||
// Limit the Merkle tree to MAX_DEPTH levels
|
||||
require(
|
||||
_treeLevels > 0 && _treeLevels <= MAX_DEPTH,
|
||||
"IncrementalMerkleTree: _treeLevels must be between 0 and 33"
|
||||
);
|
||||
|
||||
/*
|
||||
To initialise the Merkle tree, we need to calculate the Merkle root
|
||||
assuming that each leaf is the zero value.
|
||||
|
||||
H(H(a,b), H(c,d))
|
||||
/ \
|
||||
H(a,b) H(c,d)
|
||||
/ \ / \
|
||||
a b c d
|
||||
|
||||
`zeros` and `filledSubtrees` will come in handy later when we do
|
||||
inserts or updates. e.g when we insert a value in index 1, we will
|
||||
need to look up values from those arrays to recalculate the Merkle
|
||||
root.
|
||||
*/
|
||||
treeLevels = _treeLevels;
|
||||
|
||||
zeros[0] = _zeroValue;
|
||||
|
||||
uint256 currentZero = _zeroValue;
|
||||
for (uint8 i = 1; i < _treeLevels; i++) {
|
||||
uint256 hashed = hashLeftRight(currentZero, currentZero);
|
||||
zeros[i] = hashed;
|
||||
filledSubtrees[i] = hashed;
|
||||
currentZero = hashed;
|
||||
}
|
||||
|
||||
root = hashLeftRight(currentZero, currentZero);
|
||||
}
|
||||
|
||||
/*
|
||||
* Inserts a leaf into the Merkle tree and updates the root and filled
|
||||
* subtrees.
|
||||
* @param _leaf The value to insert. It must be less than the snark scalar
|
||||
* field or this function will throw.
|
||||
* @return The leaf index.
|
||||
*/
|
||||
function insertLeaf(uint256 _leaf) internal returns (uint256) {
|
||||
require(
|
||||
_leaf < SNARK_SCALAR_FIELD,
|
||||
"IncrementalMerkleTree: insertLeaf argument must be < SNARK_SCALAR_FIELD"
|
||||
);
|
||||
|
||||
uint256 currentIndex = nextLeafIndex;
|
||||
|
||||
uint256 depth = uint256(treeLevels);
|
||||
require(currentIndex < uint256(2) ** depth, "IncrementalMerkleTree: tree is full");
|
||||
|
||||
uint256 currentLevelHash = _leaf;
|
||||
uint256 left;
|
||||
uint256 right;
|
||||
|
||||
for (uint8 i = 0; i < treeLevels; i++) {
|
||||
// if current_index is 5, for instance, over the iterations it will
|
||||
// look like this: 5, 2, 1, 0, 0, 0 ...
|
||||
|
||||
if (currentIndex % 2 == 0) {
|
||||
// For later values of `i`, use the previous hash as `left`, and
|
||||
// the (hashed) zero value for `right`
|
||||
left = currentLevelHash;
|
||||
right = zeros[i];
|
||||
|
||||
filledSubtrees[i] = currentLevelHash;
|
||||
} else {
|
||||
left = filledSubtrees[i];
|
||||
right = currentLevelHash;
|
||||
}
|
||||
|
||||
currentLevelHash = hashLeftRight(left, right);
|
||||
|
||||
// equivalent to currentIndex /= 2;
|
||||
currentIndex >>= 1;
|
||||
}
|
||||
|
||||
root = currentLevelHash;
|
||||
rootHistory[root] = true;
|
||||
|
||||
uint256 n = nextLeafIndex;
|
||||
nextLeafIndex += 1;
|
||||
|
||||
emit LeafInsertion(_leaf, n);
|
||||
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
/*
|
||||
* Concatenates and hashes two `uint256` values (left and right) using
|
||||
* a combination of MiMCSponge and `addmod`.
|
||||
* @param _left The first value
|
||||
* @param _right The second value
|
||||
* @return The uint256 hash of _left and _right
|
||||
*/
|
||||
function hashLeftRight(uint256 _left, uint256 _right) internal pure returns (uint256) {
|
||||
|
||||
// Solidity documentation states:
|
||||
// `addmod(uint x, uint y, uint k) returns (uint)`:
|
||||
// compute (x + y) % k where the addition is performed with arbitrary
|
||||
// precision and does not wrap around at 2**256. Assert that k != 0
|
||||
// starting from version 0.5.0.
|
||||
|
||||
uint256 R = _left;
|
||||
uint256 C = 0;
|
||||
|
||||
(R, C) = MiMC.MiMCSponge(R, 0);
|
||||
|
||||
R = addmod(R, _right, SNARK_SCALAR_FIELD);
|
||||
(R, C) = MiMC.MiMCSponge(R, C);
|
||||
|
||||
return R;
|
||||
}
|
||||
}
|
||||
35
contracts/sol/IncrementalMerkleTreeClient.sol
Normal file
35
contracts/sol/IncrementalMerkleTreeClient.sol
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import { IncrementalMerkleTree } from './IncrementalMerkleTree.sol';
|
||||
|
||||
contract IncrementalMerkleTreeClient is IncrementalMerkleTree{
|
||||
constructor(uint8 _treeLevels, uint256 _zeroValue)
|
||||
IncrementalMerkleTree(_treeLevels, _zeroValue)
|
||||
public {
|
||||
}
|
||||
|
||||
function insertLeafAsClient(uint256 _leaf) public {
|
||||
insertLeaf(_leaf);
|
||||
}
|
||||
}
|
||||
30
contracts/sol/MiMC.sol
Normal file
30
contracts/sol/MiMC.sol
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
library MiMC {
|
||||
// Note that this function could also be called "MiMCFeistel", but we name
|
||||
// it "MiMCSponge" for consistency.
|
||||
function MiMCSponge(uint256 in_xL, uint256 in_xR) pure public
|
||||
returns (uint256 xL, uint256 xR);
|
||||
}
|
||||
|
||||
75
contracts/sol/Ownable.sol
Normal file
75
contracts/sol/Ownable.sol
Normal file
@@ -0,0 +1,75 @@
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
/**
|
||||
* @dev Contract module which provides a basic access control mechanism, where
|
||||
* there is an account (an owner) that can be granted exclusive access to
|
||||
* specific functions.
|
||||
*
|
||||
* This module is used through inheritance. It will make available the modifier
|
||||
* `onlyOwner`, which can be aplied to your functions to restrict their use to
|
||||
* the owner.
|
||||
*/
|
||||
contract Ownable {
|
||||
address private _owner;
|
||||
|
||||
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
|
||||
|
||||
/**
|
||||
* @dev Initializes the contract setting the deployer as the initial owner.
|
||||
*/
|
||||
constructor () internal {
|
||||
_owner = msg.sender;
|
||||
emit OwnershipTransferred(address(0), _owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the address of the current owner.
|
||||
*/
|
||||
function owner() public view returns (address) {
|
||||
return _owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Throws if called by any account other than the owner.
|
||||
*/
|
||||
modifier onlyOwner() {
|
||||
require(isOwner(), "Ownable: caller is not the owner");
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns true if the caller is the current owner.
|
||||
*/
|
||||
function isOwner() public view returns (bool) {
|
||||
return msg.sender == _owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Leaves the contract without owner. It will not be possible to call
|
||||
* `onlyOwner` functions anymore. Can only be called by the current owner.
|
||||
*
|
||||
* > Note: Renouncing ownership will leave the contract without an owner,
|
||||
* thereby removing any functionality that is only available to the owner.
|
||||
*/
|
||||
function renounceOwnership() public onlyOwner {
|
||||
emit OwnershipTransferred(_owner, address(0));
|
||||
_owner = address(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Transfers ownership of the contract to a new account (`newOwner`).
|
||||
* Can only be called by the current owner.
|
||||
*/
|
||||
function transferOwnership(address newOwner) public onlyOwner {
|
||||
_transferOwnership(newOwner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Transfers ownership of the contract to a new account (`newOwner`).
|
||||
*/
|
||||
function _transferOwnership(address newOwner) internal {
|
||||
require(newOwner != address(0), "Ownable: new owner is the zero address");
|
||||
emit OwnershipTransferred(_owner, newOwner);
|
||||
_owner = newOwner;
|
||||
}
|
||||
}
|
||||
486
contracts/sol/Semaphore.sol
Normal file
486
contracts/sol/Semaphore.sol
Normal file
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import "./verifier.sol";
|
||||
import { IncrementalMerkleTree } from "./IncrementalMerkleTree.sol";
|
||||
import "./Ownable.sol";
|
||||
|
||||
contract Semaphore is Verifier, IncrementalMerkleTree, Ownable {
|
||||
// The external nullifier helps to prevent double-signalling by the same
|
||||
// user. An external nullifier can be active or deactivated.
|
||||
|
||||
// Each node in the linked list
|
||||
struct ExternalNullifierNode {
|
||||
uint232 next;
|
||||
bool exists;
|
||||
bool isActive;
|
||||
}
|
||||
|
||||
// We store the external nullifiers using a mapping of the form:
|
||||
// enA => { next external nullifier; if enA exists; if enA is active }
|
||||
// Think of it as a linked list.
|
||||
mapping (uint232 => ExternalNullifierNode) public
|
||||
externalNullifierLinkedList;
|
||||
|
||||
uint256 public numExternalNullifiers = 0;
|
||||
|
||||
// First and last external nullifiers for linked list enumeration
|
||||
uint232 public firstExternalNullifier = 0;
|
||||
uint232 public lastExternalNullifier = 0;
|
||||
|
||||
// Whether broadcastSignal() can only be called by the owner of this
|
||||
// contract. This is the case as a safe default.
|
||||
bool public isBroadcastPermissioned = true;
|
||||
|
||||
// Whether the contract has already seen a particular nullifier hash
|
||||
mapping (uint256 => bool) public nullifierHashHistory;
|
||||
|
||||
event PermissionSet(bool indexed newPermission);
|
||||
event ExternalNullifierAdd(uint232 indexed externalNullifier);
|
||||
event ExternalNullifierChangeStatus(
|
||||
uint232 indexed externalNullifier,
|
||||
bool indexed active
|
||||
);
|
||||
|
||||
// This value should be equal to
|
||||
// 0x7d10c03d1f7884c85edee6353bd2b2ffbae9221236edde3778eac58089912bc0
|
||||
// which you can calculate using the following ethersjs code:
|
||||
// ethers.utils.solidityKeccak256(['bytes'], [ethers.utils.toUtf8Bytes('Semaphore')])
|
||||
// By setting the value of unset (empty) tree leaves to this
|
||||
// nothing-up-my-sleeve value, the authors hope to demonstrate that they do
|
||||
// not have its preimage and therefore cannot spend funds they do not own.
|
||||
|
||||
uint256 public NOTHING_UP_MY_SLEEVE_ZERO =
|
||||
uint256(keccak256(abi.encodePacked('Semaphore'))) % SNARK_SCALAR_FIELD;
|
||||
|
||||
/*
|
||||
* If broadcastSignal is permissioned, check if msg.sender is the contract
|
||||
* owner
|
||||
*/
|
||||
modifier onlyOwnerIfPermissioned() {
|
||||
require(
|
||||
!isBroadcastPermissioned || isOwner(),
|
||||
"Semaphore: broadcast permission denied"
|
||||
);
|
||||
|
||||
_;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param _treeLevels The depth of the identity tree.
|
||||
* @param _firstExternalNullifier The first identity nullifier to add.
|
||||
*/
|
||||
constructor(uint8 _treeLevels, uint232 _firstExternalNullifier)
|
||||
IncrementalMerkleTree(_treeLevels, NOTHING_UP_MY_SLEEVE_ZERO)
|
||||
Ownable()
|
||||
public {
|
||||
addEn(_firstExternalNullifier, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Registers a new user.
|
||||
* @param _identity_commitment The user's identity commitment, which is the
|
||||
* hash of their public key and their identity
|
||||
* nullifier (a random 31-byte value). It should
|
||||
* be the output of a Pedersen hash. It is the
|
||||
* responsibility of the caller to verify this.
|
||||
*/
|
||||
function insertIdentity(uint256 _identityCommitment) public onlyOwner
|
||||
returns (uint256) {
|
||||
// Ensure that the given identity commitment is not the zero value
|
||||
require(
|
||||
_identityCommitment != NOTHING_UP_MY_SLEEVE_ZERO,
|
||||
"Semaphore: identity commitment cannot be the nothing-up-my-sleeve-value"
|
||||
);
|
||||
|
||||
return insertLeaf(_identityCommitment);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if all values within pi_a, pi_b, and pi_c of a zk-SNARK are less
|
||||
* than the scalar field.
|
||||
* @param _a The corresponding `a` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
* @param _b The corresponding `b` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
* @param _c The corresponding `c` parameter to verifier.sol's
|
||||
verifyProof()
|
||||
*/
|
||||
function areAllValidFieldElements(
|
||||
uint256[8] memory _proof
|
||||
) internal pure returns (bool) {
|
||||
return
|
||||
_proof[0] < SNARK_SCALAR_FIELD &&
|
||||
_proof[1] < SNARK_SCALAR_FIELD &&
|
||||
_proof[2] < SNARK_SCALAR_FIELD &&
|
||||
_proof[3] < SNARK_SCALAR_FIELD &&
|
||||
_proof[4] < SNARK_SCALAR_FIELD &&
|
||||
_proof[5] < SNARK_SCALAR_FIELD &&
|
||||
_proof[6] < SNARK_SCALAR_FIELD &&
|
||||
_proof[7] < SNARK_SCALAR_FIELD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Produces a keccak256 hash of the given signal, shifted right by 8 bits.
|
||||
* @param _signal The signal to hash
|
||||
*/
|
||||
function hashSignal(bytes memory _signal) internal pure returns (uint256) {
|
||||
return uint256(keccak256(_signal)) >> 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenience function which returns a uint256 array of 8 elements which
|
||||
* comprise a Groth16 zk-SNARK proof's pi_a, pi_b, and pi_c values.
|
||||
* @param _a The corresponding `a` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
* @param _b The corresponding `b` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
* @param _c The corresponding `c` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
*/
|
||||
function packProof (
|
||||
uint256[2] memory _a,
|
||||
uint256[2][2] memory _b,
|
||||
uint256[2] memory _c
|
||||
) public pure returns (uint256[8] memory) {
|
||||
|
||||
return [
|
||||
_a[0],
|
||||
_a[1],
|
||||
_b[0][0],
|
||||
_b[0][1],
|
||||
_b[1][0],
|
||||
_b[1][1],
|
||||
_c[0],
|
||||
_c[1]
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenience function which converts an array of 8 elements, generated
|
||||
* by packProof(), into a format which verifier.sol's verifyProof()
|
||||
* accepts.
|
||||
* @param _proof The proof elements.
|
||||
*/
|
||||
function unpackProof(
|
||||
uint256[8] memory _proof
|
||||
) public pure returns (
|
||||
uint256[2] memory,
|
||||
uint256[2][2] memory,
|
||||
uint256[2] memory
|
||||
) {
|
||||
|
||||
return (
|
||||
[_proof[0], _proof[1]],
|
||||
[
|
||||
[_proof[2], _proof[3]],
|
||||
[_proof[4], _proof[5]]
|
||||
],
|
||||
[_proof[6], _proof[7]]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* A convenience view function which helps operators to easily verify all
|
||||
* inputs to broadcastSignal() using a single contract call. This helps
|
||||
* them to save gas by detecting invalid inputs before they invoke
|
||||
* broadcastSignal(). Note that this function does the same checks as
|
||||
* `isValidSignalAndProof` but returns a bool instead of using require()
|
||||
* statements.
|
||||
* @param _signal The signal to broadcast
|
||||
* @param _proof The proof elements.
|
||||
* @param _root The Merkle tree root
|
||||
* @param _nullifiersHash The nullifiers hash
|
||||
* @param _signalHash The signal hash. This is included so as to verify in
|
||||
* Solidity that the signal hash computed off-chain
|
||||
* matches.
|
||||
* @param _externalNullifier The external nullifier
|
||||
*/
|
||||
function preBroadcastCheck (
|
||||
bytes memory _signal,
|
||||
uint256[8] memory _proof,
|
||||
uint256 _root,
|
||||
uint256 _nullifiersHash,
|
||||
uint256 _signalHash,
|
||||
uint232 _externalNullifier
|
||||
) public view returns (bool) {
|
||||
|
||||
uint256[4] memory publicSignals =
|
||||
[_root, _nullifiersHash, _signalHash, _externalNullifier];
|
||||
|
||||
(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c) =
|
||||
unpackProof(_proof);
|
||||
|
||||
return nullifierHashHistory[_nullifiersHash] == false &&
|
||||
hashSignal(_signal) == _signalHash &&
|
||||
_signalHash == hashSignal(_signal) &&
|
||||
isExternalNullifierActive(_externalNullifier) &&
|
||||
rootHistory[_root] &&
|
||||
areAllValidFieldElements(_proof) &&
|
||||
_root < SNARK_SCALAR_FIELD &&
|
||||
_nullifiersHash < SNARK_SCALAR_FIELD &&
|
||||
verifyProof(a, b, c, publicSignals);
|
||||
}
|
||||
|
||||
/*
|
||||
* A modifier which ensures that the signal and proof are valid.
|
||||
* @param _signal The signal to broadcast
|
||||
* @param _proof The proof elements.
|
||||
* @param _root The Merkle tree root
|
||||
* @param _nullifiersHash The nullifiers hash
|
||||
* @param _signalHash The signal hash
|
||||
* @param _externalNullifier The external nullifier
|
||||
*/
|
||||
modifier isValidSignalAndProof (
|
||||
bytes memory _signal,
|
||||
uint256[8] memory _proof,
|
||||
uint256 _root,
|
||||
uint256 _nullifiersHash,
|
||||
uint232 _externalNullifier
|
||||
) {
|
||||
// Check whether each element in _proof is a valid field element. Even
|
||||
// if verifier.sol does this check too, it is good to do so here for
|
||||
// the sake of good protocol design.
|
||||
require(
|
||||
areAllValidFieldElements(_proof),
|
||||
"Semaphore: invalid field element(s) in proof"
|
||||
);
|
||||
|
||||
// Check whether the nullifier hash has been seen
|
||||
require(
|
||||
nullifierHashHistory[_nullifiersHash] == false,
|
||||
"Semaphore: nullifier already seen"
|
||||
);
|
||||
|
||||
// Check whether the nullifier hash is active
|
||||
require(
|
||||
isExternalNullifierActive(_externalNullifier),
|
||||
"Semaphore: external nullifier not found"
|
||||
);
|
||||
|
||||
// Check whether the given Merkle root has been seen previously
|
||||
require(rootHistory[_root], "Semaphore: root not seen");
|
||||
|
||||
uint256 signalHash = hashSignal(_signal);
|
||||
|
||||
// Check whether _nullifiersHash is a valid field element.
|
||||
require(
|
||||
_nullifiersHash < SNARK_SCALAR_FIELD,
|
||||
"Semaphore: the nullifiers hash must be lt the snark scalar field"
|
||||
);
|
||||
|
||||
uint256[4] memory publicSignals =
|
||||
[_root, _nullifiersHash, signalHash, _externalNullifier];
|
||||
|
||||
(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c) =
|
||||
unpackProof(_proof);
|
||||
|
||||
require(
|
||||
verifyProof(a, b, c, publicSignals),
|
||||
"Semaphore: invalid proof"
|
||||
);
|
||||
|
||||
// Note that we don't need to check if signalHash is less than
|
||||
// SNARK_SCALAR_FIELD because it always holds true due to the
|
||||
// definition of hashSignal()
|
||||
|
||||
_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Broadcasts the signal.
|
||||
* @param _signal The signal to broadcast
|
||||
* @param _proof The proof elements.
|
||||
* @param _root The root of the Merkle tree (the 1st public signal)
|
||||
* @param _nullifiersHash The nullifiers hash (the 2nd public signal)
|
||||
* @param _externalNullifier The nullifiers hash (the 4th public signal)
|
||||
*/
|
||||
function broadcastSignal(
|
||||
bytes memory _signal,
|
||||
uint256[8] memory _proof,
|
||||
uint256 _root,
|
||||
uint256 _nullifiersHash,
|
||||
uint232 _externalNullifier
|
||||
) public
|
||||
onlyOwnerIfPermissioned
|
||||
isValidSignalAndProof(
|
||||
_signal, _proof, _root, _nullifiersHash, _externalNullifier
|
||||
)
|
||||
{
|
||||
// Client contracts should be responsible for storing the signal and/or
|
||||
// emitting it as an event
|
||||
|
||||
// Store the nullifiers hash to prevent double-signalling
|
||||
nullifierHashHistory[_nullifiersHash] = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* A private helper function which adds an external nullifier.
|
||||
* @param _externalNullifier The external nullifier to add.
|
||||
* @param _isFirst Whether _externalNullifier is the first external
|
||||
* nullifier. Only the constructor should set _isFirst to true when it
|
||||
* calls addEn().
|
||||
*/
|
||||
function addEn(uint232 _externalNullifier, bool isFirst) private {
|
||||
if (isFirst) {
|
||||
firstExternalNullifier = _externalNullifier;
|
||||
} else {
|
||||
// The external nullifier must not have already been set
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].exists == false,
|
||||
"Semaphore: external nullifier already set"
|
||||
);
|
||||
|
||||
// Connect the previously added external nullifier node to this one
|
||||
externalNullifierLinkedList[lastExternalNullifier].next =
|
||||
_externalNullifier;
|
||||
}
|
||||
|
||||
// Add a new external nullifier
|
||||
externalNullifierLinkedList[_externalNullifier].next = 0;
|
||||
externalNullifierLinkedList[_externalNullifier].isActive = true;
|
||||
externalNullifierLinkedList[_externalNullifier].exists = true;
|
||||
|
||||
// Set the last external nullifier to this one
|
||||
lastExternalNullifier = _externalNullifier;
|
||||
|
||||
numExternalNullifiers ++;
|
||||
|
||||
emit ExternalNullifierAdd(_externalNullifier);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds an external nullifier to the contract. This external nullifier is
|
||||
* active once it is added. Only the owner can do this.
|
||||
* @param _externalNullifier The new external nullifier to set.
|
||||
*/
|
||||
function addExternalNullifier(uint232 _externalNullifier) public
|
||||
onlyOwner {
|
||||
addEn(_externalNullifier, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Deactivate an external nullifier. The external nullifier must already be
|
||||
* active for this function to work. Only the owner can do this.
|
||||
* @param _externalNullifier The new external nullifier to deactivate.
|
||||
*/
|
||||
function deactivateExternalNullifier(uint232 _externalNullifier) public
|
||||
onlyOwner {
|
||||
// The external nullifier must already exist
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].exists,
|
||||
"Semaphore: external nullifier not found"
|
||||
);
|
||||
|
||||
// The external nullifier must already be active
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].isActive == true,
|
||||
"Semaphore: external nullifier already deactivated"
|
||||
);
|
||||
|
||||
// Deactivate the external nullifier. Note that we don't change the
|
||||
// value of nextEn.
|
||||
externalNullifierLinkedList[_externalNullifier].isActive = false;
|
||||
|
||||
emit ExternalNullifierChangeStatus(_externalNullifier, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reactivate an external nullifier. The external nullifier must already be
|
||||
* inactive for this function to work. Only the owner can do this.
|
||||
* @param _externalNullifier The new external nullifier to reactivate.
|
||||
*/
|
||||
function reactivateExternalNullifier(uint232 _externalNullifier) public
|
||||
onlyOwner {
|
||||
// The external nullifier must already exist
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].exists,
|
||||
"Semaphore: external nullifier not found"
|
||||
);
|
||||
|
||||
// The external nullifier must already have been deactivated
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].isActive == false,
|
||||
"Semaphore: external nullifier is already active"
|
||||
);
|
||||
|
||||
// Reactivate the external nullifier
|
||||
externalNullifierLinkedList[_externalNullifier].isActive = true;
|
||||
|
||||
emit ExternalNullifierChangeStatus(_externalNullifier, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if and only if the specified external nullifier is active
|
||||
* @param _externalNullifier The specified external nullifier.
|
||||
*/
|
||||
function isExternalNullifierActive(uint232 _externalNullifier) public view
|
||||
returns (bool) {
|
||||
return externalNullifierLinkedList[_externalNullifier].isActive;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the next external nullifier after the specified external
|
||||
* nullifier in the linked list.
|
||||
* @param _externalNullifier The specified external nullifier.
|
||||
*/
|
||||
function getNextExternalNullifier(uint232 _externalNullifier) public view
|
||||
returns (uint232) {
|
||||
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].exists,
|
||||
"Semaphore: no such external nullifier"
|
||||
);
|
||||
|
||||
uint232 n = externalNullifierLinkedList[_externalNullifier].next;
|
||||
|
||||
require(
|
||||
numExternalNullifiers > 1 && externalNullifierLinkedList[n].exists,
|
||||
"Semaphore: no external nullifier exists after the specified one"
|
||||
);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the number of inserted identity commitments.
|
||||
*/
|
||||
function getNumIdentityCommitments() public view returns (uint256) {
|
||||
return nextLeafIndex;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the `isBroadcastPermissioned` storage variable, which determines
|
||||
* whether broadcastSignal can or cannot be called by only the contract
|
||||
* owner.
|
||||
* @param _newPermission True if the broadcastSignal can only be called by
|
||||
* the contract owner; and False otherwise.
|
||||
*/
|
||||
function setPermissioning(bool _newPermission) public onlyOwner {
|
||||
|
||||
isBroadcastPermissioned = _newPermission;
|
||||
|
||||
emit PermissionSet(_newPermission);
|
||||
}
|
||||
}
|
||||
106
contracts/sol/SemaphoreClient.sol
Normal file
106
contracts/sol/SemaphoreClient.sol
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import { Semaphore } from './Semaphore.sol';
|
||||
|
||||
contract SemaphoreClient {
|
||||
uint256[] public identityCommitments;
|
||||
|
||||
// A mapping of all signals broadcasted
|
||||
mapping (uint256 => bytes) public signalIndexToSignal;
|
||||
|
||||
// A mapping between signal indices to external nullifiers
|
||||
mapping (uint256 => uint256) public signalIndexToExternalNullifier;
|
||||
|
||||
// The next index of the `signalIndexToSignal` mapping
|
||||
uint256 public nextSignalIndex = 0;
|
||||
|
||||
Semaphore public semaphore;
|
||||
|
||||
event SignalBroadcastByClient(uint256 indexed signalIndex);
|
||||
|
||||
constructor(Semaphore _semaphore) public {
|
||||
semaphore = _semaphore;
|
||||
}
|
||||
|
||||
function getIdentityCommitments() public view returns (uint256 [] memory) {
|
||||
return identityCommitments;
|
||||
}
|
||||
|
||||
function getIdentityCommitment(uint256 _index) public view returns (uint256) {
|
||||
return identityCommitments[_index];
|
||||
}
|
||||
|
||||
function insertIdentityAsClient(uint256 _leaf) public {
|
||||
semaphore.insertIdentity(_leaf);
|
||||
identityCommitments.push(_leaf);
|
||||
}
|
||||
|
||||
function addExternalNullifier(uint232 _externalNullifier) public {
|
||||
semaphore.addExternalNullifier(_externalNullifier);
|
||||
}
|
||||
|
||||
function deactivateExternalNullifier(uint232 _externalNullifier) public {
|
||||
semaphore.deactivateExternalNullifier(_externalNullifier);
|
||||
}
|
||||
|
||||
function reactivateExternalNullifier(uint232 _externalNullifier) public {
|
||||
semaphore.reactivateExternalNullifier(_externalNullifier);
|
||||
}
|
||||
|
||||
function broadcastSignal(
|
||||
bytes memory _signal,
|
||||
uint256[8] memory _proof,
|
||||
uint256 _root,
|
||||
uint256 _nullifiersHash,
|
||||
uint232 _externalNullifier
|
||||
) public {
|
||||
uint256 signalIndex = nextSignalIndex;
|
||||
|
||||
// store the signal
|
||||
signalIndexToSignal[nextSignalIndex] = _signal;
|
||||
|
||||
// map the the signal index to the given external nullifier
|
||||
signalIndexToExternalNullifier[nextSignalIndex] = _externalNullifier;
|
||||
|
||||
// increment the signal index
|
||||
nextSignalIndex ++;
|
||||
|
||||
// broadcast the signal
|
||||
semaphore.broadcastSignal(_signal, _proof, _root, _nullifiersHash, _externalNullifier);
|
||||
|
||||
emit SignalBroadcastByClient(signalIndex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the external nullifier which a signal at _index broadcasted to
|
||||
* @param _index The index to use to look up the signalIndexToExternalNullifier mapping
|
||||
*/
|
||||
function getExternalNullifierBySignalIndex(uint256 _index) public view returns (uint256) {
|
||||
return signalIndexToExternalNullifier[_index];
|
||||
}
|
||||
|
||||
function setPermissioning(bool _newPermission) public {
|
||||
semaphore.setPermissioning(_newPermission);
|
||||
}
|
||||
}
|
||||
27
contracts/sol/SnarkConstants.sol
Normal file
27
contracts/sol/SnarkConstants.sol
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
contract SnarkConstants {
|
||||
// The scalar field
|
||||
uint256 internal constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
}
|
||||
231
contracts/sol/verifier.sol
Normal file
231
contracts/sol/verifier.sol
Normal file
@@ -0,0 +1,231 @@
|
||||
// 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.
|
||||
|
||||
// 2019 OKIMS
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
library Pairing {
|
||||
|
||||
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
|
||||
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 negation of p, i.e. p.plus(p.negate()) should be zero.
|
||||
*/
|
||||
function negate(G1Point memory p) internal pure returns (G1Point memory) {
|
||||
|
||||
// The prime q in the base field F_q for G1
|
||||
if (p.X == 0 && p.Y == 0) {
|
||||
return G1Point(0, 0);
|
||||
} else {
|
||||
return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @return The sum of two points of G1
|
||||
*/
|
||||
function plus(
|
||||
G1Point memory p1,
|
||||
G1Point memory p2
|
||||
) internal view returns (G1Point memory r) {
|
||||
|
||||
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)
|
||||
// Use "invalid" to make gas estimation work
|
||||
switch success case 0 { invalid() }
|
||||
}
|
||||
|
||||
require(success,"pairing-add-failed");
|
||||
}
|
||||
|
||||
/*
|
||||
* @return The product of a point on G1 and a scalar, i.e.
|
||||
* p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
|
||||
* points p.
|
||||
*/
|
||||
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
|
||||
|
||||
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)
|
||||
// Use "invalid" to make gas estimation work
|
||||
switch success case 0 { invalid() }
|
||||
}
|
||||
require (success,"pairing-mul-failed");
|
||||
}
|
||||
|
||||
/* @return The result of computing the pairing check
|
||||
* e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
|
||||
* For example,
|
||||
* pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
|
||||
*/
|
||||
function pairing(
|
||||
G1Point memory a1,
|
||||
G2Point memory a2,
|
||||
G1Point memory b1,
|
||||
G2Point memory b2,
|
||||
G1Point memory c1,
|
||||
G2Point memory c2,
|
||||
G1Point memory d1,
|
||||
G2Point memory d2
|
||||
) internal view returns (bool) {
|
||||
|
||||
G1Point[4] memory p1 = [a1, b1, c1, d1];
|
||||
G2Point[4] memory p2 = [a2, b2, c2, d2];
|
||||
|
||||
uint256 inputSize = 24;
|
||||
uint256[] memory input = new uint256[](inputSize);
|
||||
|
||||
for (uint256 i = 0; i < 4; i++) {
|
||||
uint256 j = i * 6;
|
||||
input[j + 0] = p1[i].X;
|
||||
input[j + 1] = p1[i].Y;
|
||||
input[j + 2] = p2[i].X[0];
|
||||
input[j + 3] = p2[i].X[1];
|
||||
input[j + 4] = p2[i].Y[0];
|
||||
input[j + 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)
|
||||
// Use "invalid" to make gas estimation work
|
||||
switch success case 0 { invalid() }
|
||||
}
|
||||
|
||||
require(success,"pairing-opcode-failed");
|
||||
|
||||
return out[0] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier {
|
||||
|
||||
using Pairing for *;
|
||||
|
||||
uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[5] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(uint256(19412864166662840704199897530865176132379551878490098268546775855212807511623), uint256(1659653118858063231936623514142744009632862245288902525823221677276210935161));
|
||||
vk.beta2 = Pairing.G2Point([uint256(21642731927166283717597040076624334182856179384944638206172042877912623746964), uint256(10167148937945084930725321596284210462590725859059519349238552658864205847292)], [uint256(1224040963938177736215304045934094846196618140310043882848453181887293487598), uint256(21818148805990469610055209831928752212083869699635755409679256612234566494214)]);
|
||||
vk.gamma2 = Pairing.G2Point([uint256(7084583264208126476050882701870582484117653569260992588151213710616665494315), uint256(21338791141815158629032141160990160038215366570564838558882493662897305673845)], [uint256(20409386432685531109985133572396335050408469502427908880430269312598850603009), uint256(21694931277671378411802527161940275869759588185961914055258162012593400433477)]);
|
||||
vk.delta2 = Pairing.G2Point([uint256(15974226699330331350608610622797702188540365463638301508490182464179306746479), uint256(10800304862034523735970868610105048512813625407055208260881866244104890739413)], [uint256(3115757193545898321493679843214264410358333980282409841160781532582592563749), uint256(20585865237865669840907249885451244426128970908885747090346049467936130099745)]);
|
||||
vk.IC[0] = Pairing.G1Point(uint256(15132217740731663181077706894312753465210500809816534743630331656993829080728), uint256(110196461348215931979632312103651461241391911014889357659299988542624772231));
|
||||
vk.IC[1] = Pairing.G1Point(uint256(10128725078198782996699361178651605009720713215878609701039758932704577595075), uint256(6404467707897071196443816328081887791672216217394045289711692279719912978002));
|
||||
vk.IC[2] = Pairing.G1Point(uint256(10522674726928807143308489533204590506138344033395366996236503798983177262637), uint256(4729387380831061502587298076566203099165040519549972799644442785786542168149));
|
||||
vk.IC[3] = Pairing.G1Point(uint256(17764548214378097040054627843654571919160111811296549132642271073668911279441), uint256(18047600752595374913303954050610046390396466710244400595444056973195374265781));
|
||||
vk.IC[4] = Pairing.G1Point(uint256(7483741608009854810379599415076882430339556147495509532415486196559753628073), uint256(15133497018284592127224880003457371607602553122563204208188040273053737050972));
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @returns Whether the proof is valid given the hardcoded verifying key
|
||||
* above and the public inputs
|
||||
*/
|
||||
function verifyProof(
|
||||
uint256[2] memory a,
|
||||
uint256[2][2] memory b,
|
||||
uint256[2] memory c,
|
||||
uint256[4] memory input
|
||||
) public view returns (bool r) {
|
||||
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x
|
||||
Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
|
||||
|
||||
// Make sure that proof.A, B, and C are each less than the prime q
|
||||
require(proof.A.X < PRIME_Q, "verifier-aX-gte-prime-q");
|
||||
require(proof.A.Y < PRIME_Q, "verifier-aY-gte-prime-q");
|
||||
|
||||
require(proof.B.X[0] < PRIME_Q, "verifier-bX0-gte-prime-q");
|
||||
require(proof.B.Y[0] < PRIME_Q, "verifier-bY0-gte-prime-q");
|
||||
|
||||
require(proof.B.X[1] < PRIME_Q, "verifier-bX1-gte-prime-q");
|
||||
require(proof.B.Y[1] < PRIME_Q, "verifier-bY1-gte-prime-q");
|
||||
|
||||
require(proof.C.X < PRIME_Q, "verifier-cX-gte-prime-q");
|
||||
require(proof.C.Y < PRIME_Q, "verifier-cY-gte-prime-q");
|
||||
|
||||
// Make sure that every input is less than the snark scalar field
|
||||
for (uint256 i = 0; i < input.length; i++) {
|
||||
require(input[i] < SNARK_SCALAR_FIELD,"verifier-gte-snark-scalar-field");
|
||||
vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
|
||||
}
|
||||
|
||||
vk_x = Pairing.plus(vk_x, vk.IC[0]);
|
||||
|
||||
return Pairing.pairing(
|
||||
Pairing.negate(proof.A),
|
||||
proof.B,
|
||||
vk.alfa1,
|
||||
vk.beta2,
|
||||
vk_x,
|
||||
vk.gamma2,
|
||||
proof.C,
|
||||
vk.delta2
|
||||
);
|
||||
}
|
||||
}
|
||||
220
contracts/ts/__tests__/FixedVerifier.test.ts
Normal file
220
contracts/ts/__tests__/FixedVerifier.test.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(90000)
|
||||
|
||||
const MiMC = require('@semaphore-contracts/compiled/MiMC.json')
|
||||
const Semaphore = require('@semaphore-contracts/compiled/Semaphore.json')
|
||||
const SemaphoreClient = require('@semaphore-contracts/compiled/SemaphoreClient.json')
|
||||
const hasEvent = require('etherlime/cli-commands/etherlime-test/events.js').hasEvent
|
||||
|
||||
import {
|
||||
SnarkBigInt,
|
||||
genIdentity,
|
||||
genIdentityCommitment,
|
||||
genExternalNullifier,
|
||||
genWitness,
|
||||
genCircuit,
|
||||
genProof,
|
||||
genPublicSignals,
|
||||
verifyProof,
|
||||
SnarkProvingKey,
|
||||
SnarkVerifyingKey,
|
||||
parseVerifyingKeyJson,
|
||||
formatForVerifierContract,
|
||||
} from 'libsemaphore'
|
||||
import * as etherlime from 'etherlime-lib'
|
||||
import { config } from 'semaphore-config'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as ethers from 'ethers'
|
||||
|
||||
const NUM_LEVELS = 20
|
||||
const FIRST_EXTERNAL_NULLIFIER = 0
|
||||
const SIGNAL = 'signal0'
|
||||
|
||||
const genTestAccounts = (num: number, mnemonic: string) => {
|
||||
let accounts: ethers.Wallet[] = []
|
||||
|
||||
for (let i=0; i<num; i++) {
|
||||
const p = `m/44'/60'/${i}'/0/0`
|
||||
const wallet = ethers.Wallet.fromMnemonic(mnemonic, p)
|
||||
accounts.push(wallet)
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
const accounts = genTestAccounts(2, config.chain.mnemonic)
|
||||
let semaphoreContract
|
||||
let semaphoreClientContract
|
||||
let mimcContract
|
||||
|
||||
// hex representations of all inserted identity commitments
|
||||
let insertedIdentityCommitments: string[] = []
|
||||
const activeEn = genExternalNullifier(Date.now().toString())
|
||||
const inactiveEn = genExternalNullifier(Date.now().toString())
|
||||
const invalidEn = BigInt(Math.pow(2, 232)).toString()
|
||||
|
||||
let deployer
|
||||
|
||||
describe('Semaphore', () => {
|
||||
beforeAll(async () => {
|
||||
deployer = new etherlime.JSONRPCPrivateKeyDeployer(
|
||||
accounts[0].privateKey,
|
||||
config.get('chain.url'),
|
||||
{
|
||||
gasLimit: 8800000,
|
||||
chainId: config.get('chain.chainId'),
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Deploying MiMC')
|
||||
mimcContract = await deployer.deploy(MiMC, {})
|
||||
|
||||
const libraries = {
|
||||
MiMC: mimcContract.contractAddress,
|
||||
}
|
||||
|
||||
console.log('Deploying Semaphore')
|
||||
semaphoreContract = await deployer.deploy(
|
||||
Semaphore,
|
||||
libraries,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
console.log('Deploying Semaphore Client')
|
||||
semaphoreClientContract = await deployer.deploy(
|
||||
SemaphoreClient,
|
||||
{},
|
||||
semaphoreContract.contractAddress,
|
||||
)
|
||||
|
||||
console.log('Transferring ownership of the Semaphore contract to the Semaphore Client')
|
||||
const tx = await semaphoreContract.transferOwnership(
|
||||
semaphoreClientContract.contractAddress,
|
||||
)
|
||||
|
||||
await tx.wait()
|
||||
})
|
||||
|
||||
test('insert an identity commitment', async () => {
|
||||
const identity = genIdentity()
|
||||
const identityCommitment: SnarkBigInt = genIdentityCommitment(identity)
|
||||
|
||||
const tx = await semaphoreClientContract.insertIdentityAsClient(
|
||||
identityCommitment.toString()
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
|
||||
console.log('Gas used by insertIdentityAsClient():', receipt.gasUsed.toString())
|
||||
|
||||
insertedIdentityCommitments.push('0x' + identityCommitment.toString(16))
|
||||
expect(hasEvent(receipt, semaphoreContract, 'LeafInsertion')).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('signal broadcasts', () => {
|
||||
// Load circuit, proving key, and verifying key
|
||||
const circuitPath = path.join(__dirname, '../../../circuits/build/circuit.json')
|
||||
const provingKeyPath = path.join(__dirname, '../../../circuits/build/proving_key.bin')
|
||||
const verifyingKeyPath = path.join(__dirname, '../../../circuits/build/verification_key.json')
|
||||
|
||||
const cirDef = JSON.parse(fs.readFileSync(circuitPath).toString())
|
||||
const provingKey: SnarkProvingKey = fs.readFileSync(provingKeyPath)
|
||||
const verifyingKey: SnarkVerifyingKey = parseVerifyingKeyJson(fs.readFileSync(verifyingKeyPath).toString())
|
||||
const circuit = genCircuit(cirDef)
|
||||
let identity
|
||||
let identityCommitment
|
||||
let proof
|
||||
let publicSignals
|
||||
let params
|
||||
|
||||
beforeAll(async () => {
|
||||
identity = genIdentity()
|
||||
identityCommitment = genIdentityCommitment(identity)
|
||||
|
||||
await (await semaphoreClientContract.insertIdentityAsClient(identityCommitment.toString())).wait()
|
||||
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
|
||||
const result = await genWitness(
|
||||
SIGNAL,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = formatForVerifierContract(proof, publicSignals)
|
||||
})
|
||||
|
||||
test('the proof should be valid', async () => {
|
||||
expect.assertions(1)
|
||||
const isValid = verifyProof(verifyingKey, proof, publicSignals)
|
||||
expect(isValid).toBeTruthy()
|
||||
})
|
||||
|
||||
test('the pre-broadcast check should pass', async () => {
|
||||
expect.assertions(1)
|
||||
|
||||
const check = await semaphoreContract.preBroadcastCheck(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.a,
|
||||
params.b,
|
||||
params.c,
|
||||
params.input[0],
|
||||
params.input[1],
|
||||
params.input[2],
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
expect(check).toBeTruthy()
|
||||
})
|
||||
|
||||
test('broadcastSignal with an input element above the scalar field should fail', async () => {
|
||||
expect.assertions(1)
|
||||
const size = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617')
|
||||
const oversizedInput = (BigInt(params.input[1]) + size).toString()
|
||||
try {
|
||||
await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.a,
|
||||
params.b,
|
||||
params.c,
|
||||
params.input[0],
|
||||
oversizedInput,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('verifier-gte-snark-scalar-field')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal to active external nullifier with an account with the right permissions should work', async () => {
|
||||
expect.assertions(4)
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.a,
|
||||
params.b,
|
||||
params.c,
|
||||
params.input[0],
|
||||
params.input[1],
|
||||
params.input[3],
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
console.log('Gas used by broadcastSignal():', receipt.gasUsed.toString())
|
||||
|
||||
const index = (await semaphoreClientContract.nextSignalIndex()) - 1
|
||||
const signal = await semaphoreClientContract.signalIndexToSignal(index.toString())
|
||||
|
||||
expect(ethers.utils.toUtf8String(signal)).toEqual(SIGNAL)
|
||||
|
||||
expect(hasEvent(receipt, semaphoreContract, 'SignalBroadcast')).toBeTruthy()
|
||||
expect(hasEvent(receipt, semaphoreClientContract, 'SignalBroadcastByClient')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
130
contracts/ts/__tests__/IncrementalMerkleTree.test.ts
Normal file
130
contracts/ts/__tests__/IncrementalMerkleTree.test.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(90000)
|
||||
|
||||
const MiMC = require('@semaphore-contracts/compiled/MiMC.json')
|
||||
const IncrementalMerkleTreeClient = require('@semaphore-contracts/compiled/IncrementalMerkleTreeClient.json')
|
||||
import * as etherlime from 'etherlime-lib'
|
||||
import { config } from 'semaphore-config'
|
||||
import * as ethers from 'ethers'
|
||||
import { storage, hashers, tree } from 'semaphore-merkle-tree'
|
||||
const mimcSpongeHasher = new hashers.MimcSpongeHasher()
|
||||
|
||||
const account = ethers.Wallet.fromMnemonic(config.chain.mnemonic, `m/44'/60'/0'/0/0`)
|
||||
let deployer
|
||||
let mtContract
|
||||
let mimcContract
|
||||
import {
|
||||
genIdentity,
|
||||
genIdentityCommitment,
|
||||
setupTree,
|
||||
} from 'libsemaphore'
|
||||
|
||||
const LEVELS = 20
|
||||
let tree
|
||||
|
||||
const ZERO_VALUE =
|
||||
ethers.utils.solidityKeccak256(
|
||||
['bytes'],
|
||||
[ethers.utils.toUtf8Bytes('Semaphore')]
|
||||
)
|
||||
|
||||
describe('IncrementalMerkleTree functions should match the semaphore-merkle-tree implementation', () => {
|
||||
let libraries
|
||||
|
||||
beforeAll(async () => {
|
||||
tree = setupTree(LEVELS)
|
||||
|
||||
deployer = new etherlime.JSONRPCPrivateKeyDeployer(
|
||||
account.privateKey,
|
||||
config.get('chain.url'),
|
||||
{
|
||||
gasLimit: 8800000,
|
||||
chainId: config.get('chain.chainId'),
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Deploying MiMC')
|
||||
mimcContract = await deployer.deploy(MiMC, {})
|
||||
|
||||
libraries = {
|
||||
MiMC: mimcContract.contractAddress,
|
||||
}
|
||||
})
|
||||
|
||||
test('deployment', async () => {
|
||||
console.log('Deploying IncrementalMerkleTreeClient')
|
||||
|
||||
mtContract = await deployer.deploy(
|
||||
IncrementalMerkleTreeClient,
|
||||
libraries,
|
||||
LEVELS,
|
||||
ZERO_VALUE,
|
||||
)
|
||||
|
||||
const root = await mtContract.root()
|
||||
const root2 = await tree.root()
|
||||
expect(root.toString()).toEqual(root2)
|
||||
})
|
||||
|
||||
test('deployment should fail if the specified number of levels is 0', async () => {
|
||||
try {
|
||||
await deployer.deploy(
|
||||
IncrementalMerkleTreeClient,
|
||||
libraries,
|
||||
0,
|
||||
ZERO_VALUE,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('IncrementalMerkleTree: _treeLevels must be between 0 and 33')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('initMerkleTree should fail if the specified number of levels exceeds 32', async () => {
|
||||
try {
|
||||
await deployer.deploy(
|
||||
IncrementalMerkleTreeClient,
|
||||
libraries,
|
||||
33,
|
||||
ZERO_VALUE,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('IncrementalMerkleTree: _treeLevels must be between 0 and 33')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('insertLeaf should fail if the leaf > the snark scalar field', async () => {
|
||||
const leaf = '21888242871839275222246405745257275088548364400416034343698204186575808495618'
|
||||
try {
|
||||
await mtContract.insertLeafAsClient(leaf)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('IncrementalMerkleTree: insertLeaf argument must be < SNARK_SCALAR_FIELD'))
|
||||
}
|
||||
})
|
||||
|
||||
test('insertLeaf (via insertLeafAsClient)', async () => {
|
||||
const leaf = genIdentityCommitment(genIdentity()).toString()
|
||||
const tx = await mtContract.insertLeafAsClient(leaf)
|
||||
const receipt = await tx.wait()
|
||||
|
||||
console.log('Gas used by insertLeaf:', receipt.gasUsed.toString())
|
||||
await tree.update(0, leaf)
|
||||
|
||||
const root = await mtContract.root()
|
||||
const root2 = await tree.root()
|
||||
expect(root.toString()).toEqual(root2)
|
||||
})
|
||||
|
||||
test('inserting a few leaves should work', async () => {
|
||||
for (let i = 1; i < 9; i++) {
|
||||
const leaf = genIdentityCommitment(genIdentity()).toString()
|
||||
const tx = await mtContract.insertLeafAsClient(leaf)
|
||||
const receipt = await tx.wait()
|
||||
|
||||
await tree.update(i, leaf)
|
||||
|
||||
const root = await mtContract.root()
|
||||
const root2 = await tree.root()
|
||||
expect(root.toString()).toEqual(root2)
|
||||
}
|
||||
})
|
||||
})
|
||||
581
contracts/ts/__tests__/Semaphore.test.ts
Normal file
581
contracts/ts/__tests__/Semaphore.test.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(90000)
|
||||
|
||||
const MiMC = require('@semaphore-contracts/compiled/MiMC.json')
|
||||
const Semaphore = require('@semaphore-contracts/compiled/Semaphore.json')
|
||||
const SemaphoreClient = require('@semaphore-contracts/compiled/SemaphoreClient.json')
|
||||
const hasEvent = require('etherlime/cli-commands/etherlime-test/events.js').hasEvent
|
||||
|
||||
import {
|
||||
SnarkBigInt,
|
||||
genIdentity,
|
||||
genIdentityCommitment,
|
||||
genExternalNullifier,
|
||||
genWitness,
|
||||
genCircuit,
|
||||
genProof,
|
||||
genPublicSignals,
|
||||
verifyProof,
|
||||
SnarkProvingKey,
|
||||
SnarkVerifyingKey,
|
||||
parseVerifyingKeyJson,
|
||||
genBroadcastSignalParams,
|
||||
genSignalHash,
|
||||
} from 'libsemaphore'
|
||||
import * as etherlime from 'etherlime-lib'
|
||||
import { config } from 'semaphore-config'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as ethers from 'ethers'
|
||||
|
||||
const NUM_LEVELS = 20
|
||||
const FIRST_EXTERNAL_NULLIFIER = 0
|
||||
const SIGNAL = 'signal0'
|
||||
|
||||
const genTestAccounts = (num: number, mnemonic: string) => {
|
||||
let accounts: ethers.Wallet[] = []
|
||||
|
||||
for (let i=0; i<num; i++) {
|
||||
const p = `m/44'/60'/${i}'/0/0`
|
||||
const wallet = ethers.Wallet.fromMnemonic(mnemonic, p)
|
||||
accounts.push(wallet)
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
const accounts = genTestAccounts(2, config.chain.mnemonic)
|
||||
let semaphoreContract
|
||||
let semaphoreClientContract
|
||||
let mimcContract
|
||||
|
||||
// hex representations of all inserted identity commitments
|
||||
let insertedIdentityCommitments: string[] = []
|
||||
const activeEn = genExternalNullifier('1111')
|
||||
const inactiveEn = genExternalNullifier('2222')
|
||||
|
||||
let deployer
|
||||
|
||||
describe('Semaphore', () => {
|
||||
beforeAll(async () => {
|
||||
deployer = new etherlime.JSONRPCPrivateKeyDeployer(
|
||||
accounts[0].privateKey,
|
||||
config.get('chain.url'),
|
||||
{
|
||||
gasLimit: 8800000,
|
||||
chainId: config.get('chain.chainId'),
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Deploying MiMC')
|
||||
mimcContract = await deployer.deploy(MiMC, {})
|
||||
|
||||
const libraries = {
|
||||
MiMC: mimcContract.contractAddress,
|
||||
}
|
||||
|
||||
console.log('Deploying Semaphore')
|
||||
semaphoreContract = await deployer.deploy(
|
||||
Semaphore,
|
||||
libraries,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
console.log('Deploying Semaphore Client')
|
||||
semaphoreClientContract = await deployer.deploy(
|
||||
SemaphoreClient,
|
||||
{},
|
||||
semaphoreContract.contractAddress,
|
||||
)
|
||||
|
||||
console.log('Transferring ownership of the Semaphore contract to the Semaphore Client')
|
||||
const tx = await semaphoreContract.transferOwnership(
|
||||
semaphoreClientContract.contractAddress,
|
||||
)
|
||||
|
||||
await tx.wait()
|
||||
})
|
||||
|
||||
test('Semaphore belongs to the correct owner', async () => {
|
||||
const owner = await semaphoreContract.owner()
|
||||
expect(owner).toEqual(semaphoreClientContract.contractAddress)
|
||||
})
|
||||
|
||||
test('insert an identity commitment', async () => {
|
||||
const identity = genIdentity()
|
||||
const identityCommitment: SnarkBigInt = genIdentityCommitment(identity)
|
||||
|
||||
const tx = await semaphoreClientContract.insertIdentityAsClient(
|
||||
identityCommitment.toString()
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
|
||||
const numInserted = await semaphoreContract.getNumIdentityCommitments()
|
||||
expect(numInserted.toString()).toEqual('1')
|
||||
|
||||
console.log('Gas used by insertIdentityAsClient():', receipt.gasUsed.toString())
|
||||
|
||||
insertedIdentityCommitments.push('0x' + identityCommitment.toString(16))
|
||||
expect(hasEvent(receipt, semaphoreContract, 'LeafInsertion')).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('identity insertions', () => {
|
||||
test('should be stored in the contract and retrievable via leaves()', async () => {
|
||||
expect.assertions(insertedIdentityCommitments.length + 1)
|
||||
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
expect(leaves.length).toEqual(insertedIdentityCommitments.length)
|
||||
|
||||
const leavesHex = leaves.map(BigInt)
|
||||
|
||||
for (let i = 0; i < insertedIdentityCommitments.length; i++) {
|
||||
const containsLeaf = leavesHex.indexOf(BigInt(insertedIdentityCommitments[i])) > -1
|
||||
expect(containsLeaf).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('should be stored in the contract and retrievable by enumerating leaf()', async () => {
|
||||
expect.assertions(insertedIdentityCommitments.length)
|
||||
|
||||
// Assumes that insertedIdentityCommitments has the same number of
|
||||
// elements as the number of leaves
|
||||
const idCommsBigint = insertedIdentityCommitments.map(BigInt)
|
||||
for (let i = 0; i < insertedIdentityCommitments.length; i++) {
|
||||
const leaf = await semaphoreClientContract.getIdentityCommitment(i)
|
||||
const leafHex = BigInt(leaf.toHexString())
|
||||
expect(idCommsBigint.indexOf(leafHex) > -1).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('inserting an identity commitment of the nothing-up-my-sleeve value should fail', async () => {
|
||||
expect.assertions(1)
|
||||
const nothingUpMySleeve =
|
||||
BigInt(ethers.utils.solidityKeccak256(['bytes'], [ethers.utils.toUtf8Bytes('Semaphore')]))
|
||||
%
|
||||
BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617')
|
||||
|
||||
try {
|
||||
await semaphoreClientContract.insertIdentityAsClient(nothingUpMySleeve.toString())
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: identity commitment cannot be the nothing-up-my-sleeve-value')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('external nullifiers', () => {
|
||||
|
||||
test('when there is only 1 external nullifier, the first and last external nullifier variables should be the same', async () => {
|
||||
expect((await semaphoreContract.numExternalNullifiers()).toNumber()).toEqual(1)
|
||||
const firstEn = await semaphoreContract.firstExternalNullifier()
|
||||
const lastEn = await semaphoreContract.lastExternalNullifier()
|
||||
expect(firstEn.toString()).toEqual(lastEn.toString())
|
||||
})
|
||||
|
||||
test('getNextExternalNullifier should throw if there is only 1 external nullifier', async () => {
|
||||
expect((await semaphoreContract.numExternalNullifiers()).toNumber()).toEqual(1)
|
||||
const firstEn = await semaphoreContract.firstExternalNullifier()
|
||||
try {
|
||||
await semaphoreContract.getNextExternalNullifier(firstEn)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: no external nullifier exists after the specified one')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('should be able to add an external nullifier', async () => {
|
||||
expect.assertions(4)
|
||||
const tx = await semaphoreClientContract.addExternalNullifier(
|
||||
activeEn,
|
||||
{ gasLimit: 200000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
|
||||
expect(receipt.status).toEqual(1)
|
||||
expect(hasEvent(receipt, semaphoreContract, 'ExternalNullifierAdd')).toBeTruthy()
|
||||
|
||||
// Check if isExternalNullifierActive works
|
||||
const isActive = await semaphoreContract.isExternalNullifierActive(activeEn)
|
||||
expect(isActive).toBeTruthy()
|
||||
|
||||
// Check if numExternalNullifiers() returns the correct value
|
||||
expect((await semaphoreContract.numExternalNullifiers()).toNumber()).toEqual(2)
|
||||
})
|
||||
|
||||
test('getNextExternalNullifier should throw if there is no such external nullifier', async () => {
|
||||
try {
|
||||
await semaphoreContract.getNextExternalNullifier('876876876876')
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: no such external nullifier')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('should be able to deactivate an external nullifier', async () => {
|
||||
await (await semaphoreClientContract.addExternalNullifier(
|
||||
inactiveEn,
|
||||
{ gasLimit: 200000 },
|
||||
)).wait()
|
||||
const tx = await semaphoreClientContract.deactivateExternalNullifier(
|
||||
inactiveEn,
|
||||
{ gasLimit: 100000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeFalsy()
|
||||
})
|
||||
|
||||
test('reactivating a deactivated external nullifier and then deactivating it should work', async () => {
|
||||
expect.assertions(3)
|
||||
|
||||
// inactiveEn should be inactive
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeFalsy()
|
||||
|
||||
// reactivate inactiveEn
|
||||
let tx = await semaphoreClientContract.reactivateExternalNullifier(
|
||||
inactiveEn,
|
||||
{ gasLimit: 100000 },
|
||||
)
|
||||
await tx.wait()
|
||||
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeTruthy()
|
||||
|
||||
tx = await semaphoreClientContract.deactivateExternalNullifier(
|
||||
inactiveEn,
|
||||
{ gasLimit: 100000 },
|
||||
)
|
||||
await tx.wait()
|
||||
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeFalsy()
|
||||
})
|
||||
|
||||
test('enumerating external nullifiers should work', async () => {
|
||||
const firstEn = await semaphoreContract.firstExternalNullifier()
|
||||
const lastEn = await semaphoreContract.lastExternalNullifier()
|
||||
|
||||
const externalNullifiers: BigInt[] = [ firstEn ]
|
||||
let currentEn = firstEn
|
||||
|
||||
while (currentEn.toString() !== lastEn.toString()) {
|
||||
currentEn = await semaphoreContract.getNextExternalNullifier(currentEn)
|
||||
externalNullifiers.push(currentEn)
|
||||
}
|
||||
|
||||
expect(externalNullifiers).toHaveLength(3)
|
||||
expect(BigInt(externalNullifiers[0].toString())).toEqual(BigInt(firstEn.toString()))
|
||||
expect(BigInt(externalNullifiers[1].toString())).toEqual(BigInt(activeEn.toString()))
|
||||
expect(BigInt(externalNullifiers[2].toString())).toEqual(BigInt(inactiveEn.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
describe('signal broadcasts', () => {
|
||||
// Load circuit, proving key, and verifying key
|
||||
const circuitPath = path.join(__dirname, '../../../circuits/build/circuit.json')
|
||||
const provingKeyPath = path.join(__dirname, '../../../circuits/build/proving_key.bin')
|
||||
const verifyingKeyPath = path.join(__dirname, '../../../circuits/build/verification_key.json')
|
||||
|
||||
const cirDef = JSON.parse(fs.readFileSync(circuitPath).toString())
|
||||
const provingKey: SnarkProvingKey = fs.readFileSync(provingKeyPath)
|
||||
const verifyingKey: SnarkVerifyingKey = parseVerifyingKeyJson(fs.readFileSync(verifyingKeyPath).toString())
|
||||
const circuit = genCircuit(cirDef)
|
||||
let identity
|
||||
let identityCommitment
|
||||
let proof
|
||||
let publicSignals
|
||||
let params
|
||||
|
||||
beforeAll(async () => {
|
||||
identity = genIdentity()
|
||||
identityCommitment = genIdentityCommitment(identity)
|
||||
|
||||
await (await semaphoreClientContract.insertIdentityAsClient(identityCommitment.toString())).wait()
|
||||
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
|
||||
const result = await genWitness(
|
||||
SIGNAL,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
})
|
||||
|
||||
test('the proof should be valid', async () => {
|
||||
expect.assertions(1)
|
||||
const isValid = verifyProof(verifyingKey, proof, publicSignals)
|
||||
expect(isValid).toBeTruthy()
|
||||
})
|
||||
|
||||
test('the pre-broadcast check should pass', async () => {
|
||||
expect.assertions(1)
|
||||
|
||||
const signal = ethers.utils.toUtf8Bytes(SIGNAL)
|
||||
const check = await semaphoreContract.preBroadcastCheck(
|
||||
signal,
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
genSignalHash(signal).toString(),
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
expect(check).toBeTruthy()
|
||||
})
|
||||
|
||||
test('the pre-broadcast check with an invalid signal should fail', async () => {
|
||||
expect.assertions(1)
|
||||
|
||||
const signal = ethers.utils.toUtf8Bytes(SIGNAL)
|
||||
const check = await semaphoreContract.preBroadcastCheck(
|
||||
'0x0',
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
genSignalHash(signal).toString(),
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
expect(check).toBeFalsy()
|
||||
})
|
||||
|
||||
test('broadcastSignal with an input element above the scalar field should fail', async () => {
|
||||
expect.assertions(1)
|
||||
const size = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617')
|
||||
const oversizedInput = (BigInt(params.nullifiersHash) + size).toString()
|
||||
try {
|
||||
await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.root,
|
||||
oversizedInput,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: the nullifiers hash must be lt the snark scalar field')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal with an invalid proof_data should fail', async () => {
|
||||
expect.assertions(1)
|
||||
try {
|
||||
await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
[
|
||||
"21888242871839275222246405745257275088548364400416034343698204186575808495617",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
],
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: invalid field element(s) in proof')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal with an unseen root should fail', async () => {
|
||||
expect.assertions(1)
|
||||
try {
|
||||
await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.nullifiersHash, // note that this is delibrately swapped
|
||||
params.root,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: root not seen')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal by an unpermissioned user should fail', async () => {
|
||||
expect.assertions(1)
|
||||
try {
|
||||
await semaphoreContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: broadcast permission denied')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal to active external nullifier with an account with the right permissions should work', async () => {
|
||||
expect.assertions(3)
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
//params.externalNullifier,
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
console.log('Gas used by broadcastSignal():', receipt.gasUsed.toString())
|
||||
|
||||
const index = (await semaphoreClientContract.nextSignalIndex()) - 1
|
||||
const signal = await semaphoreClientContract.signalIndexToSignal(index.toString())
|
||||
|
||||
expect(ethers.utils.toUtf8String(signal)).toEqual(SIGNAL)
|
||||
|
||||
expect(hasEvent(receipt, semaphoreClientContract, 'SignalBroadcastByClient')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('double-signalling to the same external nullifier should fail', async () => {
|
||||
expect.assertions(1)
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
const newSignal = 'newSignal0'
|
||||
|
||||
const result = await genWitness(
|
||||
newSignal,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
try {
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(newSignal),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: nullifier already seen')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('signalling to a different external nullifier should work', async () => {
|
||||
expect.assertions(1)
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
const newSignal = 'newSignal1'
|
||||
|
||||
const result = await genWitness(
|
||||
newSignal,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
activeEn,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(newSignal),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
activeEn,
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
})
|
||||
|
||||
test('broadcastSignal to a deactivated external nullifier should fail', async () => {
|
||||
expect.assertions(2)
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeFalsy()
|
||||
|
||||
identity = genIdentity()
|
||||
identityCommitment = genIdentityCommitment(identity)
|
||||
|
||||
await (await semaphoreClientContract.insertIdentityAsClient(identityCommitment.toString())).wait()
|
||||
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
|
||||
const result = await genWitness(
|
||||
SIGNAL,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
inactiveEn,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
|
||||
try {
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
inactiveEn,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: external nullifier not found')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('setPermissioning(false) should allow anyone to broadcast a signal', async () => {
|
||||
expect.assertions(2)
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
const newSignal = 'newSignal2'
|
||||
|
||||
const result = await genWitness(
|
||||
newSignal,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
activeEn,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
try {
|
||||
await semaphoreContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(newSignal),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
activeEn,
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: broadcast permission denied')).toBeTruthy()
|
||||
}
|
||||
|
||||
await (await semaphoreClientContract.setPermissioning(false, { gasLimit: 100000 })).wait()
|
||||
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(newSignal),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
activeEn,
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
18
contracts/ts/buildMiMC.ts
Normal file
18
contracts/ts/buildMiMC.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as Artifactor from 'truffle-artifactor'
|
||||
const mimcGenContract = require('circomlib/src/mimcsponge_gencontract.js');
|
||||
const artifactor = new Artifactor('compiled/')
|
||||
const SEED = 'mimcsponge'
|
||||
|
||||
const buildMiMC = async () => {
|
||||
await artifactor.save({
|
||||
contractName: 'MiMC',
|
||||
abi: mimcGenContract.abi,
|
||||
unlinked_binary: mimcGenContract.createCode(SEED, 220),
|
||||
})
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
buildMiMC()
|
||||
}
|
||||
|
||||
export default buildMiMC
|
||||
0
contracts/ts/index.ts
Normal file
0
contracts/ts/index.ts
Normal file
9
contracts/tsconfig.json
Normal file
9
contracts/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": [
|
||||
"./ts"
|
||||
]
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier16 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[13406811599156507528361773763681356312643537981039994686313383243831956396116, 16243966861079634958125511652590761846958471358623040426599000904006426210032],
|
||||
[11781596534582143578120404722739278517564025497573071755253972265891888117374, 15688083679237922164673518758181461582601853873216319711156397437601833996222]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
1964404930528116823793003656764176108669615750422202377358993070935069307720,
|
||||
2137714996673694828207437580381836490878070731768805974506391024595988817424
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
19568893707760843340848992184233194433177372925415116053368211122719346671126,
|
||||
11639469568629189918046964192305250472192697612201524135560178632824282818614
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
5317268879687484957437879782519918549127939892210247573193613900261494313825,
|
||||
528174394975085006443543773707702838726735933116136102590448357278717993744
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
14865918005176722116473730206622066845866539143554731094374354951675249722731,
|
||||
3197770568483953664363740385883457803041685902965668289308665954510373380344
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
6863358721495494421022713667808247652425178970453300712435830652679038918987,
|
||||
15025816433373311798308762709072064417001390853103872064614174594927359131281
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier17 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[15629200772768268814959330350023920183087521275477047626405113853190187031523, 13589689305661231568162336263197960570915890299814486885851912452076929115480],
|
||||
[11464919285924930973853174493551975632739604254498590354200272115844983493029, 16004221700357242255845535848024178544616388017965468694776181247983831995562]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
17789438292552571310739605737896030466581277887660997531707911256058650850910,
|
||||
4112657509505371631825493224748310061184972897405589115208158208294581472016
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
3322052920119834475842380240689494113984887785733316517680891208549118967155,
|
||||
381029395779795399840019487059126246243641886087320875571067736504031557148
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
8777645223617381095463415690983421308854368583891690388850387317049320450400,
|
||||
11923582117369144413749726090967341613266070909169947059497952692052020331958
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
15493263571528401950994933073246603557158047091963487223668240334879173885581,
|
||||
6315532173951617115856055775098532808695228294437279844344466163873167020700
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
3481637421055377106140197938175958155334313900824697193932986771017625492245,
|
||||
20088416136090515091300914661950097694450984520235647990572441134215240947932
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier18 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[9218320951536642499143228327011901814587826948504871816273184688188019956292, 19717684456458906358368865507225121991585492363133107109865920739019288468011],
|
||||
[16717590750910963405756115910371408378114896008824240863060392362901176601412, 18221695645112467945186983098720611586049108689347006136423489099202471884089]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
4691595252082380256698158158199364410440273386659834000993210659508747323919,
|
||||
9205801980459323513061837717352821162780471027241700646145937351740096374660
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
16150531426263112884093068164597994126623437929929609532055221646496813246000,
|
||||
20245743178241899668170758952526381872637304119026868520579207157118516761827
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
6063536446992770713985314309889717594240410784717230886576072989709763902848,
|
||||
18258781411255795973918859665416013869184055573057512603788635470145328981347
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
10109932964756104512054045207253535333686585863745296080906925765480296575285,
|
||||
4174640428253153601540284363759502713687021920150940723252842152556151210349
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
18049428534741480832385046397049175120355008065781483226058177421025493210952,
|
||||
591730261265040164434889324846001338201068482543108348317417391345612814922
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier19 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[3995128789564535587814512245259203300137618476815456454931286633947953135662, 15953239752392927777442331623182226063776310198012173504208557434319753428770],
|
||||
[20957319343912866335583737646657534123362052690050674068142580221965936605075, 2523786679709693946058523307330825034772478122295850507521258983130425334580]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
9877211178693075145402462781884120278654771727348087433632224794894486095150,
|
||||
19972682062587174829535281061580296764150591339640180868104711395548066529340
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
6324578424031095537345184040149690238371517387586958921377481904541316423724,
|
||||
15513931720576048544404512239839508014664224085062729779520992909505663748296
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
11371337652479737143800707796204655130812036287859296372695832558127430723628,
|
||||
11757275188600040111649009832378343123994225623498773406233261322165903848967
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
13282496583564708104981015168203451877588903263486398132954741568835583461335,
|
||||
1746144324840370907926720490289700342734912534857331743685374514401176014195
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
7993952462467372951144011615584426050192046712674662254138390197508963352374,
|
||||
5156942148925224345709309361345680948125600198010285179548841917923439945819
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier20 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[18976133691706015337908381757202123182841901611067930614519324084182946094218, 1382518990777992893805140303684642328066746531257780279226677247567004248173],
|
||||
[6627710380771660558660627878547223719795356903257079198333641681330388499309, 21806956747910197517744499423107239699428979652113081469385876768212706694581]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
19918517214839406678907482305035208173510172567546071380302965459737278553528,
|
||||
7151186077716310064777520690144511885696297127165278362082219441732663131220
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
690581125971423619528508316402701520070153774868732534279095503611995849608,
|
||||
21271996888576045810415843612869789314680408477068973024786458305950370465558
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
16461282535702132833442937829027913110152135149151199860671943445720775371319,
|
||||
2814052162479976678403678512565563275428791320557060777323643795017729081887
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
4319780315499060392574138782191013129592543766464046592208884866569377437627,
|
||||
13920930439395002698339449999482247728129484070642079851312682993555105218086
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
3554830803181375418665292545416227334138838284686406179598687755626325482686,
|
||||
5951609174746846070367113593675211691311013364421437923470787371738135276998
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier21 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[3811592683283527904145155808200366192489850711742363953668998371801696238057, 9032545080831535702239063467087720597970266046938395860207839433937324718536],
|
||||
[16308433125974933290258540904373317426123214107276055539769464205982500660715, 12429982191499850873612518410809641163252887523090441166572590809691267943605]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
9494885690931955877467315318223108618392113101843890678090902614660136056680,
|
||||
11783514256715757384821021009301806722951917744219075907912683963173706887379
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
7562082660623781416745328104576133910743071878837764423695105915778139873834,
|
||||
17954307004260053757579194018551114133664721761483240877658498973152950708099
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
19338184851116432029108109461622579541195083625346674255186169347975445785058,
|
||||
38361206266360048012365562393026952048730052530888439195454086987795985927
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
21178537742782571863590222710872928190886000600239072595684369348717288330049,
|
||||
9786438258541172244884631831247223050494423968411444302812755467521949734320
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
11330504221972341797183339350494223413034293674225690456356444509688810101433,
|
||||
1490009915387901405464437253469086864085891770312035292355706249426866485365
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier22 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[9485639152672984144988597737758037391807993615552051606205480347442429414340, 17626503110323089701269363177710295379967225765713250625279671011873619640598],
|
||||
[12391874700409435648975069978280047983726144854114915177376036190441913967689, 18953587685067712486092665232725058638563458484886448540567142557894080640927]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
21791720972262589799021600767292883644106575897307484548888696814333235336885,
|
||||
11092962469758788187888592619035811117815082357439060720677582048880121542623
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
9418924955930663972575130074928583215922927562059194231976193350658171304436,
|
||||
16113558481826020406162261319744796072664750077095575593106901121115073101408
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
20054934960262983176880675919444457578562219675808407582143519621873973120773,
|
||||
14877415271301547911435683263206245199959943680225555496786470669330176961657
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
4215199263810110748751715719957184804379752373072771007598572158043965517488,
|
||||
5225943468606602818132879686778547605180105897615251160509064537462109826521
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
6250242626034734280813142093008675407723196706248829741247204621913994561803,
|
||||
1472231555266678689888727724824566171966416459791722465278225775922487343641
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier23 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[9830856103389248449121962275587399130605902703453384856543071762984116567573, 11408965575174993375815840422438995549652812400401163392501956884932167624437],
|
||||
[11814906841949499037550820576929552248172160643991870665022770052632331265834, 19969543376625663966419118899515353499678204573709836615846115182224340858492]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
3047486363455933831148688762823238723024952519326207356549121929667745957778,
|
||||
20241836359289449005887237560564358543646542598344362915541027571505243817211
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
5965631918800530319167124148627450454569264331058008407732200168631989208657,
|
||||
20463557477532480934514091877628554948892025887087712764683631108388998871350
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
16605042322692983282732511249912403956057999815658038166796858627082222971215,
|
||||
12219061498275616585164456833410962809536084885494309093787669879221959361956
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
1548998572074037722622224303222294716243074837074272552644853986075252666508,
|
||||
10393312002885367652301897874262367916506364670364584602554176742602334134772
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
16180907689593358346406392015123900260925622357393826746385511046141256905390,
|
||||
12267326749885120640972074479210537480053065569337817484467225562817467244765
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +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.
|
||||
//
|
||||
// 2019 OKIMS
|
||||
// ported to solidity 0.6
|
||||
// fixed linter warnings
|
||||
// added requiere error messages
|
||||
//
|
||||
// 2021 Remco Bloemen
|
||||
// cleaned up code
|
||||
// added InvalidProve() error
|
||||
// always revert with InvalidProof() on invalid proof
|
||||
// make Pairing strict
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
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 moludus 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() internal pure returns (G1Point memory) {
|
||||
return G1Point(1, 2);
|
||||
}
|
||||
|
||||
/// @return the generator of G2
|
||||
function P2() internal 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) internal 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) internal 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) internal view returns (G1Point memory r) {
|
||||
// By EIP-196 the values p.X and p.Y are verified to less than the BASE_MODULUS and
|
||||
// form a valid point on the curve. But the scalar is not verified, so we do that explicitelly.
|
||||
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) internal 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();
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier24 {
|
||||
using Pairing for *;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(
|
||||
20491192805390485299153009773594534940189261866228447918068658471970481763042,
|
||||
9383485363053290200918347156157836566562967994039712273449902621266178545958
|
||||
);
|
||||
|
||||
vk.beta2 = Pairing.G2Point(
|
||||
[4252822878758300859123897981450591353533073413197771768651442665752259397132, 6375614351688725206403948262868962793625744043794305715222011528459656738731],
|
||||
[21847035105528745403288232691147584728191162732299865338377159692350059136679, 10505242626370262277552901082094356697409835680220590971873171140371331206856]
|
||||
);
|
||||
|
||||
vk.gamma2 = Pairing.G2Point(
|
||||
[11559732032986387107991004021392285783925812861821192530917403151452391805634, 10857046999023057135944570762232829481370756359578518086990519993285655852781],
|
||||
[4082367875863433681332203403145435568316851327593401208105741076214120093531, 8495653923123431417604973247489272438418190587263600148770280649306958101930]
|
||||
);
|
||||
|
||||
vk.delta2 = Pairing.G2Point(
|
||||
[15035335306919942325459417688135340085377315274625768597233474641923619728582, 10090041889587324002759549286390619541526396451963494627957072069124011137562],
|
||||
[21342049717074059749518233491526445388158772701642182532370641230478027030319, 10507786999799841055999967456762679569286329319056926475375760604262707147294]
|
||||
);
|
||||
|
||||
vk.IC = new Pairing.G1Point[](5);
|
||||
|
||||
|
||||
vk.IC[0] = Pairing.G1Point(
|
||||
19590996174696909242575628014943555633938195923520472786993379268302478708283,
|
||||
2673753072556442230312995111304911178679525806396134504594492458566941824354
|
||||
);
|
||||
|
||||
vk.IC[1] = Pairing.G1Point(
|
||||
13411253172375451489380472831999887223592471057462692619008484995624281735092,
|
||||
17181767455563581254432161119660408482332423481128600038352147258951772423229
|
||||
);
|
||||
|
||||
vk.IC[2] = Pairing.G1Point(
|
||||
19138864631164378176055647711995352935065134904103255748190268290992108588628,
|
||||
14282526277736365863821375748687709839392307698935143595732632710176778519757
|
||||
);
|
||||
|
||||
vk.IC[3] = Pairing.G1Point(
|
||||
20183773658676161990469276414858234178608794783112866811307579993999118293429,
|
||||
5223464433544489066271184294750886227362580875255044558831927430970236355539
|
||||
);
|
||||
|
||||
vk.IC[4] = Pairing.G1Point(
|
||||
12333466991139269670298178539679773509487545471126920233507132846828588847444,
|
||||
3787586478923104354547687861486563468235879611952775292288436085429794222238
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/// @dev Verifies a Semaphore proof. Reverts with InvalidProof if the proof is invalid.
|
||||
function verifyProof(
|
||||
uint[2] memory a,
|
||||
uint[2][2] memory b,
|
||||
uint[2] memory c,
|
||||
uint[4] memory input
|
||||
) public view {
|
||||
// If the values are not in the correct range, the Pairing contract will revert.
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x of inputs times IC
|
||||
if (input.length + 1 != vk.IC.length) revert Pairing.InvalidProof();
|
||||
Pairing.G1Point memory vk_x = vk.IC[0];
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[1], input[0]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[2], input[1]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[3], input[2]));
|
||||
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[4], input[3]));
|
||||
|
||||
// Check pairing
|
||||
Pairing.G1Point[] memory p1 = new Pairing.G1Point[](4);
|
||||
Pairing.G2Point[] memory p2 = new Pairing.G2Point[](4);
|
||||
p1[0] = Pairing.negate(proof.A);
|
||||
p2[0] = proof.B;
|
||||
p1[1] = vk.alfa1;
|
||||
p2[1] = vk.beta2;
|
||||
p1[2] = vk_x;
|
||||
p2[2] = vk.gamma2;
|
||||
p1[3] = proof.C;
|
||||
p2[3] = vk.delta2;
|
||||
Pairing.pairingCheck(p1, p2);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user