Compare commits

..

21 Commits

Author SHA1 Message Date
Koh Wei Jie
236e6b241c rebuilt docs
Former-commit-id: 6fd0ba1ed2c27c6a970460e2b34b4d23b3e46cda
2020-03-03 12:23:15 -05:00
Koh Wei Jie
e259482ad8 Merge pull request #1 from ChihChengLiang/patch-1
Fix dead link

Former-commit-id: 3b563f8b933eca517a47e79747bbb10da32de47f
2020-03-03 12:22:36 -05:00
Chih Cheng Liang
76101a0c54 Fix dead link
Former-commit-id: ff87e32053e52cb1d9b6458848deafafe76e4364
2020-03-03 17:44:32 +08:00
Koh Wei Jie
8489d149c7 updated credits page
Former-commit-id: ec103be001bde652b4104746ff2521aa3494f550
2020-03-02 15:49:49 -05:00
Koh Wei Jie
dc0298599b Merge branch 'master' of github.com:appliedzkp/semaphore into audited
Former-commit-id: 49f0eaeefe2336eda659a27ddf65adef1d314a17
2020-03-02 15:37:26 -05:00
Koh Wei Jie
55fa4203d2 added link to github repo in about.md
Former-commit-id: ded9748cf499bf4b46791745d8f6b11b3102b2db
2020-03-02 15:36:03 -05:00
Kobi Gurkan
1cd0e5f9d7 Create CNAME
Former-commit-id: 6b3e8af7e6d22c997574942418a12d582e82f40c
2020-03-02 18:55:59 +02:00
Koh Wei Jie
08961c0197 updated readme url to docs to point to appliedzkp
Former-commit-id: b6d8cb066506c7de9f39ebdfd27f2c0e7bcd0d50
2020-03-02 11:00:36 -05:00
Koh Wei Jie
bbe13aa12a formatted readme
Former-commit-id: bbf7a17fe0
2020-03-01 18:40:11 -05:00
Koh Wei Jie
d5a9ba4b60 fixed typo
Former-commit-id: 1650db69ca
2020-03-01 18:12:08 -05:00
Koh Wei Jie
ba5f2a038f added names to the credits page
Former-commit-id: 94f4caff15
2020-03-01 18:10:16 -05:00
Koh Wei Jie
375b79628e copied libsemaphore readme over to docs
Former-commit-id: 9582eb61a5
2020-03-01 17:59:15 -05:00
Koh Wei Jie
242924fff4 upgraded libsemaphore version
Former-commit-id: eb26c48d95
2020-03-01 17:20:03 -05:00
Koh Wei Jie
18e0afc711 slight tweaks to contract docstrings
Former-commit-id: 632add1f36
2020-03-01 16:45:28 -05:00
Koh Wei Jie
ab9a0e84f3 wip
Former-commit-id: b11fed59b7
2020-03-01 15:11:48 -05:00
Koh Wei Jie
be42bcf8eb restored .gitignore
Former-commit-id: ede594b50c
2020-03-01 14:13:28 -05:00
Koh Wei Jie
b3fc357ecf updated api docs; fixed readme typo
Former-commit-id: b81623ec7d
2020-02-29 17:45:31 -05:00
Koh Wei Jie
5ba953ecd0 replaced all files with the new, audited repo
Former-commit-id: 777abf1280
2020-02-29 16:25:38 -05:00
Koh Wei Jie
13121a907c replaced all files with the new, audited repo
Former-commit-id: d3044d6054
2020-02-29 16:12:48 -05:00
Koh Wei Jie
648bf9a097 first commit
Former-commit-id: e1e7d66b37
2020-02-20 10:58:08 -08:00
Koh Wei Jie
24340b0692 added mapping to associate signal indices to external nullifiers
Former-commit-id: 59202b5664
2019-10-26 22:18:24 +08:00
305 changed files with 36280 additions and 12186 deletions

85
.circleci/config.yml Normal file
View 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

View File

@@ -1,3 +0,0 @@
{
"extends": ["@commitlint/config-conventional"]
}

View File

@@ -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

View File

@@ -1,8 +0,0 @@
DEFAULT_NETWORK=hardhat
TREE_DEPTH=20
ALL_SNARK_ARTIFACTS=true
REPORT_GAS=false
BACKEND_PRIVATE_KEY=
INFURA_API_KEY=
COINMARKETCAP_API_KEY=
ETHERSCAN_API_KEY=

View File

@@ -1,37 +0,0 @@
# dependencies
node_modules
package-lock.json
yarn.lock
.yarn
# testing
coverage
coverage.json
# hardhat
artifacts
cache
typechain-types
# types
types
# circuits
circuits
# production
dist
build
docs
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# packages
cli-template-*

View File

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

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

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

View File

@@ -1,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. -->

View File

@@ -1,14 +0,0 @@
name: auto-assign
on:
pull_request:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: wow-actions/auto-assign@v3
with:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
reviewers: org/core-devs

View File

@@ -1,45 +0,0 @@
name: docs
on:
push:
branches:
- main
jobs:
gh-pages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- name: Generate doc website
run: yarn docs
- name: Publish on Github Pages
uses: crazy-max/ghaction-github-pages@v2.5.0
with:
build_dir: docs
jekyll: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

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

View File

@@ -1,43 +0,0 @@
name: release
permissions:
contents: write
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 16.x
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"
- name: Restore yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn
- run: yarn version:release
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}

99
.gitignore vendored
View File

@@ -1,91 +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
# IDE
.vscode
.idea
# Testing
coverage
coverage.json
*.lcov
# Dependency directories
node_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
.DS_Store
# Output of 'npm pack'
*.tgz
# 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
docs/*
!docs/CNAME
!docs/index.html
# Hardhat
artifacts
cache
typechain-types
packages/contracts/deployed-contracts/undefined.json
packages/contracts/deployed-contracts/hardhat.json
packages/contracts/deployed-contracts/localhost.json
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Other
snark-artifacts
docs_src/book/
.etherlime-store
**/build/
circuits/build
contracts/compiled
node_modules
blake2sdef.json
build/.snark_checksum

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no-install commitlint --edit $1

View File

@@ -1,3 +0,0 @@
{
"**/*.{js,ts}": ["prettier --write", "eslint --fix"]
}

View File

@@ -1,43 +0,0 @@
# dependencies
node_modules
package-lock.json
yarn.lock
.yarn
# testing
coverage
coverage.json
# hardhat
artifacts
cache
typechain-types
packages/contracts/deployed-contracts/undefined.json
packages/contracts/deployed-contracts/hardhat.json
packages/contracts/deployed-contracts/localhost.json
# circuits
circuits
# contracts
Verifier*.sol
# production
dist
build
docs
# github
.github/ISSUE_TEMPLATE
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# other
snark-artifacts

View File

@@ -1,5 +0,0 @@
{
"semi": false,
"arrowParens": "always",
"trailingComma": "none"
}

View File

@@ -1,23 +0,0 @@
diff --git a/dist/shared/changelogithub.821fab93.mjs b/dist/shared/changelogithub.821fab93.mjs
index 5fc2100867613c20f7827eac8715a5fc28bdc39e..97bd8dff878b81c63d2220e496904f6f3933589a 100644
--- a/dist/shared/changelogithub.821fab93.mjs
+++ b/dist/shared/changelogithub.821fab93.mjs
@@ -181,7 +181,7 @@ function formatLine(commit, options) {
function formatTitle(name, options) {
if (!options.emoji)
name = name.replace(emojisRE, "");
- return `### &nbsp;&nbsp;&nbsp;${name.trim()}`;
+ return `## &nbsp;&nbsp;&nbsp;${name.trim()}`;
}
function formatSection(commits, sectionName, options) {
if (!commits.length)
@@ -198,7 +198,8 @@ function formatSection(commits, sectionName, options) {
Object.keys(scopes).sort().forEach((scope) => {
let padding = "";
let prefix = "";
- const scopeText = `**${options.scopeMap[scope] || scope}**`;
+ const url = `https://github.com/${options.github}/tree/main/packages/${scope}`
+ const scopeText = `[**@${options.github.split("/")[0]}/${options.scopeMap[scope] || scope}**](${url})`
if (scope && useScopeGroup) {
lines.push(`- ${scopeText}:`);
padding = " ";

View File

@@ -1 +0,0 @@
87de4f440a77841135f97a187e09140c6d4e6ae2

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
b3cadff6efb37a12712d12c2553ec703dbcaa4dd

View File

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

View File

@@ -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.

View File

@@ -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 must contain the name of the package you are working on.
#### 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
View File

@@ -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.

356
README.md
View File

@@ -1,350 +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="/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%3Aproduction">
<img alt="GitHub Workflow test" src="https://img.shields.io/github/actions/workflow/status/semaphore-protocol/semaphore/production.yml?branch=main&label=test&style=flat-square&logo=github">
</a>
<a href="https://coveralls.io/github/semaphore-protocol/semaphore">
<img alt="Coveralls" src="https://img.shields.io/coveralls/github/semaphore-protocol/semaphore?style=flat-square&logo=coveralls">
</a>
<a href="https://deepscan.io/dashboard#view=project&tid=16502&pid=22324&bid=657461">
<img src="https://deepscan.io/api/teams/16502/projects/22324/branches/657461/badge/grade.svg" alt="DeepScan grade">
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint">
</a>
<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">
<a href="https://www.gitpoap.io/gh/semaphore-protocol/semaphore" target="_blank">
<img src="https://public-api.gitpoap.io/v1/repo/semaphore-protocol/semaphore/badge">
</a>
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.
</p>
For more information, refer to the
[documentation](https://appliedzkp.github.io/semaphore/).
<div align="center">
<h4>
<a href="/CONTRIBUTING.md">
👥 Contributing
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="/CODE_OF_CONDUCT.md">
🤝 Code of conduct
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>
</div>
| Semaphore is a protocol, designed to be a simple and generic privacy layer for Ethereum DApps. Using zero knowledge, Ethereum users can prove their membership of a group and send signals such as votes or endorsements without revealing their original identity. |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
The core of the Semaphore protocol is in the [circuit logic](/packages/circuits/scheme.png). However Semaphore also provides [Solidity contracts](/packages/contracts) and JavaScript libraries to make the steps for offchain proof creation and onchain verification easier. To learn more about Semaphore visit [semaphore.appliedzkp.org](https://semaphore.appliedzkp.org).
---
## 📦 Packages
<table>
<th>Package</th>
<th>Version</th>
<th>Downloads</th>
<tbody>
<tr>
<td>
<a href="/packages/contracts">
@semaphore-protocol/contracts
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/contracts">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/contracts.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/contracts">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/contracts.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/identity">
@semaphore-protocol/identity
</a>
<a href="https://semaphore-protocol.github.io/semaphore/identity">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/identity">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/identity.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/identity">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/identity.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/group">
@semaphore-protocol/group
</a>
<a href="https://semaphore-protocol.github.io/semaphore/group">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/group">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/group.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/group">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/group.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/proof">
@semaphore-protocol/proof
</a>
<a href="https://semaphore-protocol.github.io/semaphore/proof">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/proof">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/proof.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/proof">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/proof.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/data">
@semaphore-protocol/data
</a>
<a href="https://semaphore-protocol.github.io/semaphore/data">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/data">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/data.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/data">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/data.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/hardhat">
@semaphore-protocol/hardhat
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/hardhat">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/hardhat.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/hardhat">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/hardhat.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/cli">
@semaphore-protocol/cli
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/cli">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/cli.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/cli">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/cli.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tr>
<td>
<a href="/packages/heyauthn">
@semaphore-protocol/heyauthn
</a>
<a href="https://semaphore-protocol.github.io/semaphore/heyauthn">
(docs)
</a>
</td>
<td>
<!-- NPM version -->
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
<img src="https://img.shields.io/npm/v/@semaphore-protocol/heyauthn.svg?style=flat-square" alt="NPM version" />
</a>
</td>
<td>
<!-- Downloads -->
<a href="https://npmjs.org/package/@semaphore-protocol/heyauthn">
<img src="https://img.shields.io/npm/dm/@semaphore-protocol/heyauthn.svg?style=flat-square" alt="Downloads" />
</a>
</td>
</tr>
<tbody>
</table>
## 🛠 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
```
### Testing
Run [Jest](https://jestjs.io/) to test the JS libraries:
```bash
yarn test:libraries
```
Run [Mocha](https://mochajs.org/) to test the contracts:
```bash
yarn test:contracts
```
Or test everything with:
```bash
yarn test
```
### Build libraries & compile contracts
Run [Rollup](https://www.rollupjs.org) to build all the packages:
```bash
yarn build:libraries
```
Compile the smart contracts with [Hardhat](https://hardhat.org/):
```bash
yarn compile:contracts
```
### Documentation (JS libraries)
Run [TypeDoc](https://typedoc.org/) to generate a documentation website for each package:
```bash
yarn docs
```
The output will be placed on the `docs` folder.
Join the [Telegram group](https://t.me/joinchat/B-PQx1U3GtAh--Z4Fwo56A) to discuss.

View File

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

View File

@@ -1,7 +0,0 @@
{
"types": {
"feat": { "title": "🚀 Features" },
"fix": { "title": "🐞 Bug Fixes" },
"refactor": { "title": "♻️ Refactoring" }
}
}

View 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];
}
}
}
}
}
}

View 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];
}
}

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

View File

@@ -0,0 +1,3 @@
include "./semaphore-base.circom";
component main = Semaphore(20);

29
circuits/jest.config.js Normal file
View 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

File diff suppressed because it is too large Load Diff

24
circuits/package.json Normal file
View 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"
}
}

View 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/

View 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

View 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

View File

@@ -0,0 +1,6 @@
#!/bin/bash -xe
cd "$(dirname "$0")"
cd ..
npm run test-blake2s

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

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

View File

@@ -0,0 +1,3 @@
include "../../../circom/blake2s/blake2s.circom";
component main = Blake2sCompression(5, 0);

View File

@@ -0,0 +1,3 @@
include "../../../circom/blake2s/blake2s.circom";
component main = Blake2s(8, 0);

View 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);

View File

@@ -0,0 +1,3 @@
include "../../../circom/blake2s/uint32.circom";
component main = Uint32Add(5);

View File

@@ -0,0 +1,3 @@
include "../../../circom/blake2s/uint32.circom";
component main = Uint32Xor();

11
circuits/ts/bin2hex.ts Normal file
View 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
View 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
View File

9
circuits/tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "./build"
},
"include": [
"./ts"
]
}

7
config/local-dev.yaml Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "./build"
},
"include": [
"./ts"
]
}

32
contracts/jest.config.js Normal file
View 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'
}

View File

@@ -0,0 +1 @@
a22dd75fdaddd98d93ccf3467f78af832dc94864

39
contracts/package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"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": {
"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
View 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
View 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

View 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

View 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;
}
}

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

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

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

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

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

View 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
View 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
View File

9
contracts/tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "./build"
},
"include": [
"./ts"
]
}

1
docs/.nojekyll Normal file
View File

@@ -0,0 +1 @@
This file makes sure that Github Pages doesn't process mdBook's output.

4
docs/FontAwesome/css/font-awesome.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

293
docs/about.html Normal file
View File

@@ -0,0 +1,293 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>About - </title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
<ol class="chapter"><li class="expanded "><a href="about.html" class="active"><strong aria-hidden="true">1.</strong> About</a></li><li class="expanded "><a href="howitworks.html"><strong aria-hidden="true">2.</strong> How it works</a></li><li class="expanded "><a href="quickstart.html"><strong aria-hidden="true">3.</strong> Quick start</a></li><li class="expanded "><a href="usage.html"><strong aria-hidden="true">4.</strong> Usage</a></li><li class="expanded "><a href="api.html"><strong aria-hidden="true">5.</strong> Contract API</a></li><li class="expanded "><a href="libsemaphore.html"><strong aria-hidden="true">6.</strong> libsemaphore</a></li><li class="expanded "><a href="trustedsetup.html"><strong aria-hidden="true">7.</strong> Trusted setup</a></li><li class="expanded "><a href="audit.html"><strong aria-hidden="true">8.</strong> Security audit</a></li><li class="expanded "><a href="creditsandresources.html"><strong aria-hidden="true">9.</strong> Credits and resources</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title"></h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1><a class="header" href="#about" id="about">About</a></h1>
<p><a href="https://github.com/appliedzkp/semaphore">Semaphore</a> is a zero-knowledge gadget
which allows Ethereum users to prove their membership of a set which they had
previously joined 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.
Finally, it provides a simple built-in mechanism to prevent double-signalling
or double-spending.</p>
<p>This gadget comprises of smart contracts and
<a href="https://z.cash/technology/zksnarks/">zero-knowledge</a> components which work in
tandem. The Semaphore smart contract handles state, permissions, and proof
verification on-chain. The zero-knowledge components work off-chain to allow
the user to generate proofs, which allow the smart contract to update its state
if these proofs are valid.</p>
<p>Semaphore is designed for smart contract and dApp developers, not end users.
Developers should abstract its features away in order to provide user-friendly
privacy.</p>
<p>Try a simple demo <a href="https://weijiekoh.github.io/semaphore-ui/">here</a> or read a
high-level description of Semaphore
<a href="https://medium.com/coinmonks/to-mixers-and-beyond-presenting-semaphore-a-privacy-gadget-built-on-ethereum-4c8b00857c9b">here</a>.</p>
<h2><a class="header" href="#basic-features" id="basic-features">Basic features</a></h2>
<p>In sum, Semaphore provides the ability to:</p>
<ol>
<li>
<p>Register an identity in a smart contract, and then:</p>
</li>
<li>
<p>Broadcast a signal:</p>
<ul>
<li>
<p>Anonymously prove that their identity is in the set of registered
identities, and at the same time:</p>
</li>
<li>
<p>Publicly store an arbitrary string in the contract, if and only if that
string is unique to the user and the contracts current external
nullifier, which is a unique value akin to a topic. This means that
double-signalling the same message under the same external nullifier is
not possible.</p>
</li>
</ul>
</li>
</ol>
<h3><a class="header" href="#about-external-nullifiers" id="about-external-nullifiers">About external nullifiers</a></h3>
<p>Think of an external nullifier as a voting booth where each user may only cast
one vote. If they try to cast a second vote a the same booth, that vote is
invalid.</p>
<p>An external nullifier is any 29-byte value. Semaphore always starts with one
external nullifier, which is set upon contract deployment. The owner of the
Semaphore contract may add more external nullifiers, deactivate, or reactivate
existing ones.</p>
<p>The first time a particular user broadcasts a signal to an active external
nullifier <code>n</code>, and if the user's proof of membership of the set of registered
users is valid, the transaction will succeed. The second time she does so to
the same <code>n</code>, however, her transaction will fail.</p>
<p>Additionally, all signals broadcast transactions to a deactivated external
nullifier will fail.</p>
<p>Each client application must use the above features of Semaphore in a unique
way to achieve its privacy goals. A mixer, for instance, would use one external
nullifier as such:</p>
<table><thead><tr><th>Signal</th><th>External nullifier</th></tr></thead><tbody>
<tr><td>The hash of the recipient's address, relayer's address, and the relayer's fee</td><td>The mixer contract's address</td></tr>
</tbody></table>
<p>This allows anonymous withdrawals of funds (via a transaction relayer, who is
rewarded with a fee), and prevents double-spending as there is only one
external nullifier.</p>
<p>An anonymous voting app would be configured differently:</p>
<table><thead><tr><th>Signal</th><th>External nullifier</th></tr></thead><tbody>
<tr><td>The hash of the respondent's answer</td><td>The hash of the question</td></tr>
</tbody></table>
<p>This allows any user to vote with an arbitary response (e.g. yes, no, or maybe)
to any question. The user, however, can only vote once per question.</p>
<h2><a class="header" href="#about-the-code" id="about-the-code">About the code</a></h2>
<p>This repository contains the code for Semaphore's contracts written in
Soliidty, and zk-SNARK circuits written in
<a href="https://github.com/iden3/circom">circom</a>. It also contains Typescript code to
execute tests.</p>
<p>The code has been audited by ABDK Consulting. Their suggested security and
efficiency fixes have been applied.</p>
<p>A multi-party computation to produce the zk-SNARK proving and verification keys
for Semaphore will begin in the near future.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="next" href="howitworks.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="howitworks.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playpen_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>

299
docs/api.html Normal file
View File

@@ -0,0 +1,299 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Contract API - </title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
<ol class="chapter"><li class="expanded "><a href="about.html"><strong aria-hidden="true">1.</strong> About</a></li><li class="expanded "><a href="howitworks.html"><strong aria-hidden="true">2.</strong> How it works</a></li><li class="expanded "><a href="quickstart.html"><strong aria-hidden="true">3.</strong> Quick start</a></li><li class="expanded "><a href="usage.html"><strong aria-hidden="true">4.</strong> Usage</a></li><li class="expanded "><a href="api.html" class="active"><strong aria-hidden="true">5.</strong> Contract API</a></li><li class="expanded "><a href="libsemaphore.html"><strong aria-hidden="true">6.</strong> libsemaphore</a></li><li class="expanded "><a href="trustedsetup.html"><strong aria-hidden="true">7.</strong> Trusted setup</a></li><li class="expanded "><a href="audit.html"><strong aria-hidden="true">8.</strong> Security audit</a></li><li class="expanded "><a href="creditsandresources.html"><strong aria-hidden="true">9.</strong> Credits and resources</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title"></h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1><a class="header" href="#contract-api" id="contract-api">Contract API</a></h1>
<h2><a class="header" href="#constructor" id="constructor">Constructor</a></h2>
<p><strong>Contract ABI</strong>:</p>
<p><code>constructor(uint8 _treeLevels, uint232 _firstExternalNullifier)</code></p>
<ul>
<li><code>_treeLevels</code>: The depth of the identity tree.</li>
<li><code>_firstExternalNullifier</code>: The first identity nullifier to add.</li>
</ul>
<p>The depth of the identity tree determines how many identity commitments may be
added to this contract: <code>2 ^ _treeLevels</code>. Once the tree is full, further
insertions will fail with the revert reason <code>IncrementalMerkleTree: tree is full</code>.</p>
<p>The first external nullifier will be added as an external nullifier to the
contract, and this external nullifier will be active once the deployment
completes.</p>
<h2><a class="header" href="#add-deactivate-or-reactivate-external-nullifiiers" id="add-deactivate-or-reactivate-external-nullifiiers">Add, deactivate, or reactivate external nullifiiers</a></h2>
<p><strong>Contract ABI</strong>:</p>
<p><code>addExternalNullifier(uint232 _externalNullifier)</code></p>
<p>Adds an external nullifier to the contract. Only the owner can do this.
This external nullifier is active once it is added.</p>
<ul>
<li><code>_externalNullifier</code>: The new external nullifier to set.</li>
</ul>
<p><code>deactivateExternalNullifier(uint232 _externalNullifier)</code></p>
<ul>
<li><code>_externalNullifier</code>: The existing external nullifier to deactivate.</li>
</ul>
<p>Deactivate an external nullifier. The external nullifier must already be active
for this function to work. Only the owner can do this.</p>
<p><code>reactivateExternalNullifier(uint232 _externalNullifier)</code></p>
<p>Reactivate an external nullifier. The external nullifier must already be
inactive for this function to work. Only the owner can do this.</p>
<ul>
<li><code>_externalNullifier</code>: The deactivated external nullifier to reactivate.</li>
</ul>
<h2><a class="header" href="#insert-identities" id="insert-identities">Insert identities</a></h2>
<p><strong>Contract ABI</strong>:</p>
<p><code>function insertIdentity(uint256 _identityCommitment)</code></p>
<ul>
<li><code>_identity_commitment</code>: 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.</li>
</ul>
<p><strong>Off-chain <code>libsemaphore</code> helper functions</strong>:</p>
<p>Use <code>genIdentity()</code> to generate an <code>Identity</code> object, and
<code>genIdentityCommitment(identity: Identity)</code> to generate the
<code>_identityCommitment</code> value to pass to the contract.</p>
<p>To convert <code>identity</code> to a string and back, so that you can store it in a
database or somewhere safe, use <code>serialiseIdentity()</code> and
<code>unSerialiseIdentity()</code>.</p>
<p>See the <a href="./usage.html#insert-identities">Usage section on inserting
identities</a> for more information.</p>
<h2><a class="header" href="#broadcast-signals" id="broadcast-signals">Broadcast signals</a></h2>
<p><strong>Contract ABI</strong>:</p>
<pre><code>broadcastSignal(
bytes memory _signal,
uint256[8] memory _proof,
uint256 _root,
uint256 _nullifiersHash,
uint232 _externalNullifier
)
</code></pre>
<ul>
<li><code>_signal</code>: the signal to broadcast.</li>
<li><code>_proof</code>: a zk-SNARK proof (see below).</li>
<li><code>_root</code>: The root of the identity tree, where the user's identity commitment
is the last-inserted leaf.</li>
<li><code>_nullifiersHash</code>: A uniquely derived hash of the external nullifier, user's
identity nullifier, and the Merkle path index to their identity commitment.
It ensures that a user cannot broadcast a signal with the same external
nullifier more than once.</li>
<li><code>_externalNullifier</code>: The external nullifier at which the signal is
broadcast.</li>
</ul>
<p><strong>Off-chain <code>libsemaphore</code> helper functions</strong>:</p>
<p>Use <code>libsemaphore</code>'s <code>genWitness()</code>, <code>genProof()</code>, <code>genPublicSignals()</code> and
finally <code>genBroadcastSignalParams()</code> to generate the parameters to the
contract's <code>broadcastSignal()</code> function.</p>
<p>See the <a href="./usage.html#broadcast-signals">Usage section on broadcasting
signals</a> for more information.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="usage.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="libsemaphore.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="usage.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a href="libsemaphore.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playpen_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>

228
docs/audit.html Normal file
View File

@@ -0,0 +1,228 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Security audit - </title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
<ol class="chapter"><li class="expanded "><a href="about.html"><strong aria-hidden="true">1.</strong> About</a></li><li class="expanded "><a href="howitworks.html"><strong aria-hidden="true">2.</strong> How it works</a></li><li class="expanded "><a href="quickstart.html"><strong aria-hidden="true">3.</strong> Quick start</a></li><li class="expanded "><a href="usage.html"><strong aria-hidden="true">4.</strong> Usage</a></li><li class="expanded "><a href="api.html"><strong aria-hidden="true">5.</strong> Contract API</a></li><li class="expanded "><a href="libsemaphore.html"><strong aria-hidden="true">6.</strong> libsemaphore</a></li><li class="expanded "><a href="trustedsetup.html"><strong aria-hidden="true">7.</strong> Trusted setup</a></li><li class="expanded "><a href="audit.html" class="active"><strong aria-hidden="true">8.</strong> Security audit</a></li><li class="expanded "><a href="creditsandresources.html"><strong aria-hidden="true">9.</strong> Credits and resources</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title"></h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1><a class="header" href="#security-audit" id="security-audit">Security audit</a></h1>
<p>The <a href="https://ethereum.org/">Ethereum Foundation</a> and <a href="https://www.poa.network/">POA
Network</a> commissioned <a href="https://www.abdk.consulting">ABDK
Consulting</a> to audit the source code of Semaphore
as well as relevant circuits in
<a href="https://github.com/iden3/circomlib">circomlib</a>, which contains components
which the Semaphore zk-SNARK uses.</p>
<p>All security and performance issues have been fixed. The full audit report will
be available soon.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="trustedsetup.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="creditsandresources.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="trustedsetup.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a href="creditsandresources.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playpen_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>

79
docs/ayu-highlight.css Normal file
View File

@@ -0,0 +1,79 @@
/*
Based off of the Ayu theme
Original by Dempfi (https://github.com/dempfi/ayu)
*/
.hljs {
display: block;
overflow-x: auto;
background: #191f26;
color: #e6e1cf;
padding: 0.5em;
}
.hljs-comment,
.hljs-quote,
.hljs-meta {
color: #5c6773;
font-style: italic;
}
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-attr,
.hljs-regexp,
.hljs-link,
.hljs-selector-id,
.hljs-selector-class {
color: #ff7733;
}
.hljs-number,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #ffee99;
}
.hljs-string,
.hljs-bullet {
color: #b8cc52;
}
.hljs-title,
.hljs-built_in,
.hljs-section {
color: #ffb454;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-symbol {
color: #ff7733;
}
.hljs-name {
color: #36a3d9;
}
.hljs-tag {
color: #00568d;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-addition {
color: #91b362;
}
.hljs-deletion {
color: #d96c75;
}

605
docs/book.js Normal file
View File

@@ -0,0 +1,605 @@
"use strict";
// Fix back button cache problem
window.onunload = function () { };
// Global variable, shared between modules
function playpen_text(playpen) {
let code_block = playpen.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
let editor = window.ace.edit(code_block);
return editor.getValue();
} else {
return code_block.textContent;
}
}
(function codeSnippets() {
function fetch_with_timeout(url, options, timeout = 6000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
]);
}
var playpens = Array.from(document.querySelectorAll(".playpen"));
if (playpens.length > 0) {
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
headers: {
'Content-Type': "application/json",
},
method: 'POST',
mode: 'cors',
})
.then(response => response.json())
.then(response => {
// get list of crates available in the rust playground
let playground_crates = response.crates.map(item => item["id"]);
playpens.forEach(block => handle_crate_list_update(block, playground_crates));
});
}
function handle_crate_list_update(playpen_block, playground_crates) {
// update the play buttons after receiving the response
update_play_button(playpen_block, playground_crates);
// and install on change listener to dynamically update ACE editors
if (window.ace) {
let code_block = playpen_block.querySelector("code");
if (code_block.classList.contains("editable")) {
let editor = window.ace.edit(code_block);
editor.addEventListener("change", function (e) {
update_play_button(playpen_block, playground_crates);
});
// add Ctrl-Enter command to execute rust code
editor.commands.addCommand({
name: "run",
bindKey: {
win: "Ctrl-Enter",
mac: "Ctrl-Enter"
},
exec: _editor => run_rust_code(playpen_block)
});
}
}
}
// updates the visibility of play button based on `no_run` class and
// used crates vs ones available on http://play.rust-lang.org
function update_play_button(pre_block, playground_crates) {
var play_button = pre_block.querySelector(".play-button");
// skip if code is `no_run`
if (pre_block.querySelector('code').classList.contains("no_run")) {
play_button.classList.add("hidden");
return;
}
// get list of `extern crate`'s from snippet
var txt = playpen_text(pre_block);
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
var snippet_crates = [];
var item;
while (item = re.exec(txt)) {
snippet_crates.push(item[1]);
}
// check if all used crates are available on play.rust-lang.org
var all_available = snippet_crates.every(function (elem) {
return playground_crates.indexOf(elem) > -1;
});
if (all_available) {
play_button.classList.remove("hidden");
} else {
play_button.classList.add("hidden");
}
}
function run_rust_code(code_block) {
var result_block = code_block.querySelector(".result");
if (!result_block) {
result_block = document.createElement('code');
result_block.className = 'result hljs language-bash';
code_block.append(result_block);
}
let text = playpen_text(code_block);
let classes = code_block.querySelector('code').classList;
let has_2018 = classes.contains("edition2018");
let edition = has_2018 ? "2018" : "2015";
var params = {
version: "stable",
optimize: "0",
code: text,
edition: edition
};
if (text.indexOf("#![feature") !== -1) {
params.version = "nightly";
}
result_block.innerText = "Running...";
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
headers: {
'Content-Type': "application/json",
},
method: 'POST',
mode: 'cors',
body: JSON.stringify(params)
})
.then(response => response.json())
.then(response => result_block.innerText = response.result)
.catch(error => result_block.innerText = "Playground Communication: " + error.message);
}
// Syntax highlighting Configuration
hljs.configure({
tabReplace: ' ', // 4 spaces
languages: [], // Languages used for auto-detection
});
if (window.ace) {
// language-rust class needs to be removed for editable
// blocks or highlightjs will capture events
Array
.from(document.querySelectorAll('code.editable'))
.forEach(function (block) { block.classList.remove('language-rust'); });
Array
.from(document.querySelectorAll('code:not(.editable)'))
.forEach(function (block) { hljs.highlightBlock(block); });
} else {
Array
.from(document.querySelectorAll('code'))
.forEach(function (block) { hljs.highlightBlock(block); });
}
// Adding the hljs class gives code blocks the color css
// even if highlighting doesn't apply
Array
.from(document.querySelectorAll('code'))
.forEach(function (block) { block.classList.add('hljs'); });
Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
var lines = Array.from(block.querySelectorAll('.boring'));
// If no lines were hidden, return
if (!lines.length) { return; }
block.classList.add("hide-boring");
var buttons = document.createElement('div');
buttons.className = 'buttons';
buttons.innerHTML = "<button class=\"fa fa-expand\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
// add expand button
var pre_block = block.parentNode;
pre_block.insertBefore(buttons, pre_block.firstChild);
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
if (e.target.classList.contains('fa-expand')) {
e.target.classList.remove('fa-expand');
e.target.classList.add('fa-compress');
e.target.title = 'Hide lines';
e.target.setAttribute('aria-label', e.target.title);
block.classList.remove('hide-boring');
} else if (e.target.classList.contains('fa-compress')) {
e.target.classList.remove('fa-compress');
e.target.classList.add('fa-expand');
e.target.title = 'Show hidden lines';
e.target.setAttribute('aria-label', e.target.title);
block.classList.add('hide-boring');
}
});
});
if (window.playpen_copyable) {
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
var pre_block = block.parentNode;
if (!pre_block.classList.contains('playpen')) {
var buttons = pre_block.querySelector(".buttons");
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
var clipButton = document.createElement('button');
clipButton.className = 'fa fa-copy clip-button';
clipButton.title = 'Copy to clipboard';
clipButton.setAttribute('aria-label', clipButton.title);
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
buttons.insertBefore(clipButton, buttons.firstChild);
}
});
}
// Process playpen code blocks
Array.from(document.querySelectorAll(".playpen")).forEach(function (pre_block) {
// Add play button
var buttons = pre_block.querySelector(".buttons");
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
var runCodeButton = document.createElement('button');
runCodeButton.className = 'fa fa-play play-button';
runCodeButton.hidden = true;
runCodeButton.title = 'Run this code';
runCodeButton.setAttribute('aria-label', runCodeButton.title);
buttons.insertBefore(runCodeButton, buttons.firstChild);
runCodeButton.addEventListener('click', function (e) {
run_rust_code(pre_block);
});
if (window.playpen_copyable) {
var copyCodeClipboardButton = document.createElement('button');
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
copyCodeClipboardButton.title = 'Copy to clipboard';
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
}
let code_block = pre_block.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
var undoChangesButton = document.createElement('button');
undoChangesButton.className = 'fa fa-history reset-button';
undoChangesButton.title = 'Undo changes';
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
buttons.insertBefore(undoChangesButton, buttons.firstChild);
undoChangesButton.addEventListener('click', function () {
let editor = window.ace.edit(code_block);
editor.setValue(editor.originalCode);
editor.clearSelection();
});
}
});
})();
(function themes() {
var html = document.querySelector('html');
var themeToggleButton = document.getElementById('theme-toggle');
var themePopup = document.getElementById('theme-list');
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
var stylesheets = {
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
highlight: document.querySelector("[href$='highlight.css']"),
};
function showThemes() {
themePopup.style.display = 'block';
themeToggleButton.setAttribute('aria-expanded', true);
themePopup.querySelector("button#" + document.body.className).focus();
}
function hideThemes() {
themePopup.style.display = 'none';
themeToggleButton.setAttribute('aria-expanded', false);
themeToggleButton.focus();
}
function set_theme(theme, store = true) {
let ace_theme;
if (theme == 'coal' || theme == 'navy') {
stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = false;
stylesheets.highlight.disabled = true;
ace_theme = "ace/theme/tomorrow_night";
} else if (theme == 'ayu') {
stylesheets.ayuHighlight.disabled = false;
stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = true;
ace_theme = "ace/theme/tomorrow_night";
} else {
stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = false;
ace_theme = "ace/theme/dawn";
}
setTimeout(function () {
themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
}, 1);
if (window.ace && window.editors) {
window.editors.forEach(function (editor) {
editor.setTheme(ace_theme);
});
}
var previousTheme;
try { previousTheme = localStorage.getItem('mdbook-theme'); } catch (e) { }
if (previousTheme === null || previousTheme === undefined) { previousTheme = default_theme; }
if (store) {
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
}
html.classList.remove(previousTheme);
html.classList.add(theme);
}
// Set theme
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
set_theme(theme, false);
themeToggleButton.addEventListener('click', function () {
if (themePopup.style.display === 'block') {
hideThemes();
} else {
showThemes();
}
});
themePopup.addEventListener('click', function (e) {
var theme = e.target.id || e.target.parentElement.id;
set_theme(theme);
});
themePopup.addEventListener('focusout', function(e) {
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
hideThemes();
}
});
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
document.addEventListener('click', function(e) {
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
hideThemes();
}
});
document.addEventListener('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
if (!themePopup.contains(e.target)) { return; }
switch (e.key) {
case 'Escape':
e.preventDefault();
hideThemes();
break;
case 'ArrowUp':
e.preventDefault();
var li = document.activeElement.parentElement;
if (li && li.previousElementSibling) {
li.previousElementSibling.querySelector('button').focus();
}
break;
case 'ArrowDown':
e.preventDefault();
var li = document.activeElement.parentElement;
if (li && li.nextElementSibling) {
li.nextElementSibling.querySelector('button').focus();
}
break;
case 'Home':
e.preventDefault();
themePopup.querySelector('li:first-child button').focus();
break;
case 'End':
e.preventDefault();
themePopup.querySelector('li:last-child button').focus();
break;
}
});
})();
(function sidebar() {
var html = document.querySelector("html");
var sidebar = document.getElementById("sidebar");
var sidebarScrollBox = document.getElementById("sidebar-scrollbox");
var sidebarLinks = document.querySelectorAll('#sidebar a');
var sidebarToggleButton = document.getElementById("sidebar-toggle");
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
var firstContact = null;
function showSidebar() {
html.classList.remove('sidebar-hidden')
html.classList.add('sidebar-visible');
Array.from(sidebarLinks).forEach(function (link) {
link.setAttribute('tabIndex', 0);
});
sidebarToggleButton.setAttribute('aria-expanded', true);
sidebar.setAttribute('aria-hidden', false);
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
}
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
function toggleSection(ev) {
ev.currentTarget.parentElement.classList.toggle('expanded');
}
Array.from(sidebarAnchorToggles).forEach(function (el) {
el.addEventListener('click', toggleSection);
});
function hideSidebar() {
html.classList.remove('sidebar-visible')
html.classList.add('sidebar-hidden');
Array.from(sidebarLinks).forEach(function (link) {
link.setAttribute('tabIndex', -1);
});
sidebarToggleButton.setAttribute('aria-expanded', false);
sidebar.setAttribute('aria-hidden', true);
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
}
// Toggle sidebar
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
if (html.classList.contains("sidebar-hidden")) {
showSidebar();
} else if (html.classList.contains("sidebar-visible")) {
hideSidebar();
} else {
if (getComputedStyle(sidebar)['transform'] === 'none') {
hideSidebar();
} else {
showSidebar();
}
}
});
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
function initResize(e) {
window.addEventListener('mousemove', resize, false);
window.addEventListener('mouseup', stopResize, false);
html.classList.add('sidebar-resizing');
}
function resize(e) {
document.documentElement.style.setProperty('--sidebar-width', (e.clientX - sidebar.offsetLeft) + 'px');
}
//on mouseup remove windows functions mousemove & mouseup
function stopResize(e) {
html.classList.remove('sidebar-resizing');
window.removeEventListener('mousemove', resize, false);
window.removeEventListener('mouseup', stopResize, false);
}
document.addEventListener('touchstart', function (e) {
firstContact = {
x: e.touches[0].clientX,
time: Date.now()
};
}, { passive: true });
document.addEventListener('touchmove', function (e) {
if (!firstContact)
return;
var curX = e.touches[0].clientX;
var xDiff = curX - firstContact.x,
tDiff = Date.now() - firstContact.time;
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
showSidebar();
else if (xDiff < 0 && curX < 300)
hideSidebar();
firstContact = null;
}
}, { passive: true });
// Scroll sidebar to current active section
var activeSection = sidebar.querySelector(".active");
if (activeSection) {
sidebarScrollBox.scrollTop = activeSection.offsetTop;
}
})();
(function chapterNavigation() {
document.addEventListener('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
if (window.search && window.search.hasFocus()) { return; }
switch (e.key) {
case 'ArrowRight':
e.preventDefault();
var nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) {
window.location.href = nextButton.href;
}
break;
case 'ArrowLeft':
e.preventDefault();
var previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) {
window.location.href = previousButton.href;
}
break;
}
});
})();
(function clipboard() {
var clipButtons = document.querySelectorAll('.clip-button');
function hideTooltip(elem) {
elem.firstChild.innerText = "";
elem.className = 'fa fa-copy clip-button';
}
function showTooltip(elem, msg) {
elem.firstChild.innerText = msg;
elem.className = 'fa fa-copy tooltipped';
}
var clipboardSnippets = new ClipboardJS('.clip-button', {
text: function (trigger) {
hideTooltip(trigger);
let playpen = trigger.closest("pre");
return playpen_text(playpen);
}
});
Array.from(clipButtons).forEach(function (clipButton) {
clipButton.addEventListener('mouseout', function (e) {
hideTooltip(e.currentTarget);
});
});
clipboardSnippets.on('success', function (e) {
e.clearSelection();
showTooltip(e.trigger, "Copied!");
});
clipboardSnippets.on('error', function (e) {
showTooltip(e.trigger, "Clipboard error!");
});
})();
(function scrollToTop () {
var menuTitle = document.querySelector('.menu-title');
menuTitle.addEventListener('click', function () {
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
});
})();
(function autoHideMenu() {
var menu = document.getElementById('menu-bar');
var previousScrollTop = document.scrollingElement.scrollTop;
document.addEventListener('scroll', function () {
if (menu.classList.contains('folded') && document.scrollingElement.scrollTop < previousScrollTop) {
menu.classList.remove('folded');
} else if (!menu.classList.contains('folded') && document.scrollingElement.scrollTop > previousScrollTop) {
menu.classList.add('folded');
}
if (!menu.classList.contains('bordered') && document.scrollingElement.scrollTop > 0) {
menu.classList.add('bordered');
}
if (menu.classList.contains('bordered') && document.scrollingElement.scrollTop === 0) {
menu.classList.remove('bordered');
}
previousScrollTop = Math.max(document.scrollingElement.scrollTop, 0);
}, { passive: true });
})();

7
docs/clipboard.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,234 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Credits and resources - </title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div id="sidebar-scrollbox" class="sidebar-scrollbox">
<ol class="chapter"><li class="expanded "><a href="about.html"><strong aria-hidden="true">1.</strong> About</a></li><li class="expanded "><a href="howitworks.html"><strong aria-hidden="true">2.</strong> How it works</a></li><li class="expanded "><a href="quickstart.html"><strong aria-hidden="true">3.</strong> Quick start</a></li><li class="expanded "><a href="usage.html"><strong aria-hidden="true">4.</strong> Usage</a></li><li class="expanded "><a href="api.html"><strong aria-hidden="true">5.</strong> Contract API</a></li><li class="expanded "><a href="libsemaphore.html"><strong aria-hidden="true">6.</strong> libsemaphore</a></li><li class="expanded "><a href="trustedsetup.html"><strong aria-hidden="true">7.</strong> Trusted setup</a></li><li class="expanded "><a href="audit.html"><strong aria-hidden="true">8.</strong> Security audit</a></li><li class="expanded "><a href="creditsandresources.html" class="active"><strong aria-hidden="true">9.</strong> Credits and resources</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title"></h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1><a class="header" href="#credits" id="credits">Credits</a></h1>
<ul>
<li>Barry WhiteHat</li>
<li>Chih Cheng Liang</li>
<li>Kobi Gurkan</li>
<li>Koh Wei Jie</li>
<li>Harry Roberts</li>
</ul>
<p>Many thanks to:</p>
<ul>
<li>ABDK Consulting</li>
<li>Jordi Baylina / iden3</li>
<li>POA Network</li>
<li>PepperSec</li>
<li>Ethereum Foundation</li>
</ul>
<h1><a class="header" href="#resources" id="resources">Resources</a></h1>
<p><a href="https://medium.com/coinmonks/to-mixers-and-beyond-presenting-semaphore-a-privacy-gadget-built-on-ethereum-4c8b00857c9b">To Mixers and Beyond: presenting Semaphore, a privacy gadget built on Ethereum</a> - Koh Wei Jie</p>
<p><a href="https://www.youtube.com/watch?v=maDHYyj30kg">Privacy in Ethereum</a> - Barry WhiteHat at the Taipei Ethereum Meetup</p>
<p><a href="https://www.youtube.com/watch?v=lv6iK9qezBY">Snarks for mixing, signaling and scaling by</a> - Barry WhiteHat at Devcon 4</p>
<p><a href="https://www.youtube.com/watch?v=zBUo7G95wYE">Privacy in Ethereum</a> - Barry WhiteHat at Devcon 5</p>
<p><a href="https://www.youtube.com/watch?v=GzVT16lFOHU">A trustless Ethereum mixer using zero-knowledge signalling</a> - Koh Wei Jie and Barry WhiteHat at Devcon 5</p>
<p><a href="https://www.youtube.com/watch?v=7wd2aAN2jXI">Hands-on Applications of Zero-Knowledge Signalling</a> - Koh Wei Jie at Devcon 5</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="audit.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="audit.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playpen_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>

484
docs/css/chrome.css Normal file
View File

@@ -0,0 +1,484 @@
/* CSS for UI elements (a.k.a. chrome) */
@import 'variables.css';
::-webkit-scrollbar {
background: var(--bg);
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar);
}
html {
scrollbar-color: var(--scrollbar) var(--bg);
}
#searchresults a,
.content a:link,
a:visited,
a > .hljs {
color: var(--links);
}
/* Menu Bar */
#menu-bar {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 101;
margin: auto calc(0px - var(--page-padding));
}
#menu-bar > #menu-bar-sticky-container {
display: flex;
flex-wrap: wrap;
background-color: var(--bg);
border-bottom-color: var(--bg);
border-bottom-width: 1px;
border-bottom-style: solid;
}
.js #menu-bar > #menu-bar-sticky-container {
transition: transform 0.3s;
}
#menu-bar.bordered > #menu-bar-sticky-container {
border-bottom-color: var(--table-border-color);
}
#menu-bar i, #menu-bar .icon-button {
position: relative;
padding: 0 8px;
z-index: 10;
line-height: var(--menu-bar-height);
cursor: pointer;
transition: color 0.5s;
}
@media only screen and (max-width: 420px) {
#menu-bar i, #menu-bar .icon-button {
padding: 0 5px;
}
}
.icon-button {
border: none;
background: none;
padding: 0;
color: inherit;
}
.icon-button i {
margin: 0;
}
.right-buttons {
margin: 0 15px;
}
.right-buttons a {
text-decoration: none;
}
html:not(.sidebar-visible) #menu-bar:not(:hover).folded > #menu-bar-sticky-container {
transform: translateY(calc(-10px - var(--menu-bar-height)));
}
.left-buttons {
display: flex;
margin: 0 5px;
}
.no-js .left-buttons {
display: none;
}
.menu-title {
display: inline-block;
font-weight: 200;
font-size: 2rem;
line-height: var(--menu-bar-height);
text-align: center;
margin: 0;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.js .menu-title {
cursor: pointer;
}
.menu-bar,
.menu-bar:visited,
.nav-chapters,
.nav-chapters:visited,
.mobile-nav-chapters,
.mobile-nav-chapters:visited,
.menu-bar .icon-button,
.menu-bar a i {
color: var(--icons);
}
.menu-bar i:hover,
.menu-bar .icon-button:hover,
.nav-chapters:hover,
.mobile-nav-chapters i:hover {
color: var(--icons-hover);
}
/* Nav Icons */
.nav-chapters {
font-size: 2.5em;
text-align: center;
text-decoration: none;
position: fixed;
top: 0;
bottom: 0;
margin: 0;
max-width: 150px;
min-width: 90px;
display: flex;
justify-content: center;
align-content: center;
flex-direction: column;
transition: color 0.5s, background-color 0.5s;
}
.nav-chapters:hover {
text-decoration: none;
background-color: var(--theme-hover);
transition: background-color 0.15s, color 0.15s;
}
.nav-wrapper {
margin-top: 50px;
display: none;
}
.mobile-nav-chapters {
font-size: 2.5em;
text-align: center;
text-decoration: none;
width: 90px;
border-radius: 5px;
background-color: var(--sidebar-bg);
}
.previous {
float: left;
}
.next {
float: right;
right: var(--page-padding);
}
@media only screen and (max-width: 1080px) {
.nav-wide-wrapper { display: none; }
.nav-wrapper { display: block; }
}
@media only screen and (max-width: 1380px) {
.sidebar-visible .nav-wide-wrapper { display: none; }
.sidebar-visible .nav-wrapper { display: block; }
}
/* Inline code */
:not(pre) > .hljs {
display: inline;
padding: 0.1em 0.3em;
border-radius: 3px;
}
:not(pre):not(a) > .hljs {
color: var(--inline-code-color);
overflow-x: initial;
}
a:hover > .hljs {
text-decoration: underline;
}
pre {
position: relative;
}
pre > .buttons {
position: absolute;
z-index: 100;
right: 5px;
top: 5px;
color: var(--sidebar-fg);
cursor: pointer;
}
pre > .buttons :hover {
color: var(--sidebar-active);
}
pre > .buttons i {
margin-left: 8px;
}
pre > .buttons button {
color: inherit;
background: transparent;
border: none;
cursor: inherit;
}
pre > .result {
margin-top: 10px;
}
/* Search */
#searchresults a {
text-decoration: none;
}
mark {
border-radius: 2px;
padding: 0 3px 1px 3px;
margin: 0 -3px -1px -3px;
background-color: var(--search-mark-bg);
transition: background-color 300ms linear;
cursor: pointer;
}
mark.fade-out {
background-color: rgba(0,0,0,0) !important;
cursor: auto;
}
.searchbar-outer {
margin-left: auto;
margin-right: auto;
max-width: var(--content-max-width);
}
#searchbar {
width: 100%;
margin: 5px auto 0px auto;
padding: 10px 16px;
transition: box-shadow 300ms ease-in-out;
border: 1px solid var(--searchbar-border-color);
border-radius: 3px;
background-color: var(--searchbar-bg);
color: var(--searchbar-fg);
}
#searchbar:focus,
#searchbar.active {
box-shadow: 0 0 3px var(--searchbar-shadow-color);
}
.searchresults-header {
font-weight: bold;
font-size: 1em;
padding: 18px 0 0 5px;
color: var(--searchresults-header-fg);
}
.searchresults-outer {
margin-left: auto;
margin-right: auto;
max-width: var(--content-max-width);
border-bottom: 1px dashed var(--searchresults-border-color);
}
ul#searchresults {
list-style: none;
padding-left: 20px;
}
ul#searchresults li {
margin: 10px 0px;
padding: 2px;
border-radius: 2px;
}
ul#searchresults li.focus {
background-color: var(--searchresults-li-bg);
}
ul#searchresults span.teaser {
display: block;
clear: both;
margin: 5px 0 0 20px;
font-size: 0.8em;
}
ul#searchresults span.teaser em {
font-weight: bold;
font-style: normal;
}
/* Sidebar */
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: var(--sidebar-width);
font-size: 0.875em;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: contain;
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
}
.sidebar-resizing {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.js:not(.sidebar-resizing) .sidebar {
transition: transform 0.3s; /* Animation: slide away */
}
.sidebar code {
line-height: 2em;
}
.sidebar .sidebar-scrollbox {
overflow-y: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 10px 10px;
}
.sidebar .sidebar-resize-handle {
position: absolute;
cursor: col-resize;
width: 0;
right: 0;
top: 0;
bottom: 0;
}
.js .sidebar .sidebar-resize-handle {
cursor: col-resize;
width: 5px;
}
.sidebar-hidden .sidebar {
transform: translateX(calc(0px - var(--sidebar-width)));
}
.sidebar::-webkit-scrollbar {
background: var(--sidebar-bg);
}
.sidebar::-webkit-scrollbar-thumb {
background: var(--scrollbar);
}
.sidebar-visible .page-wrapper {
transform: translateX(var(--sidebar-width));
}
@media only screen and (min-width: 620px) {
.sidebar-visible .page-wrapper {
transform: none;
margin-left: var(--sidebar-width);
}
}
.chapter {
list-style: none outside none;
padding-left: 0;
line-height: 2.2em;
}
.chapter ol {
width: 100%;
}
.chapter li {
display: flex;
color: var(--sidebar-non-existant);
}
.chapter li a {
display: block;
padding: 0;
text-decoration: none;
color: var(--sidebar-fg);
}
.chapter li a:hover {
color: var(--sidebar-active);
}
.chapter li a.active {
color: var(--sidebar-active);
}
.chapter li > a.toggle {
cursor: pointer;
display: block;
margin-left: auto;
padding: 0 10px;
user-select: none;
opacity: 0.68;
}
.chapter li > a.toggle div {
transition: transform 0.5s;
}
/* collapse the section */
.chapter li:not(.expanded) + li > ol {
display: none;
}
.chapter li.expanded > a.toggle div {
transform: rotate(90deg);
}
.spacer {
width: 100%;
height: 3px;
margin: 5px 0px;
}
.chapter .spacer {
background-color: var(--sidebar-spacer);
}
@media (-moz-touch-enabled: 1), (pointer: coarse) {
.chapter li a { padding: 5px 0; }
.spacer { margin: 10px 0; }
}
.section {
list-style: none outside none;
padding-left: 20px;
line-height: 1.9em;
}
/* Theme Menu Popup */
.theme-popup {
position: absolute;
left: 10px;
top: var(--menu-bar-height);
z-index: 1000;
border-radius: 4px;
font-size: 0.7em;
color: var(--fg);
background: var(--theme-popup-bg);
border: 1px solid var(--theme-popup-border);
margin: 0;
padding: 0;
list-style: none;
display: none;
}
.theme-popup .default {
color: var(--icons);
}
.theme-popup .theme {
width: 100%;
border: 0;
margin: 0;
padding: 2px 10px;
line-height: 25px;
white-space: nowrap;
text-align: left;
cursor: pointer;
color: inherit;
background: inherit;
font-size: inherit;
}
.theme-popup .theme:hover {
background-color: var(--theme-hover);
}
.theme-popup .theme:hover:first-child,
.theme-popup .theme:hover:last-child {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
}

159
docs/css/general.css Normal file
View File

@@ -0,0 +1,159 @@
/* Base styles and content styles */
@import 'variables.css';
:root {
/* Browser default font-size is 16px, this way 1 rem = 10px */
font-size: 62.5%;
}
html {
font-family: "Open Sans", sans-serif;
color: var(--fg);
background-color: var(--bg);
text-size-adjust: none;
}
body {
margin: 0;
font-size: 1.6rem;
overflow-x: hidden;
}
code {
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
}
.left { float: left; }
.right { float: right; }
.boring { opacity: 0.6; }
.hide-boring .boring { display: none; }
.hidden { display: none; }
h2, h3 { margin-top: 2.5em; }
h4, h5 { margin-top: 2em; }
.header + .header h3,
.header + .header h4,
.header + .header h5 {
margin-top: 1em;
}
h1 a.header:target::before,
h2 a.header:target::before,
h3 a.header:target::before,
h4 a.header:target::before {
display: inline-block;
content: "»";
margin-left: -30px;
width: 30px;
}
h1 a.header:target,
h2 a.header:target,
h3 a.header:target,
h4 a.header:target {
scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
}
.page {
outline: 0;
padding: 0 var(--page-padding);
}
.page-wrapper {
box-sizing: border-box;
}
.js:not(.sidebar-resizing) .page-wrapper {
transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
}
.content {
overflow-y: auto;
padding: 0 15px;
padding-bottom: 50px;
}
.content main {
margin-left: auto;
margin-right: auto;
max-width: var(--content-max-width);
}
.content a { text-decoration: none; }
.content a:hover { text-decoration: underline; }
.content img { max-width: 100%; }
.content .header:link,
.content .header:visited {
color: var(--fg);
}
.content .header:link,
.content .header:visited:hover {
text-decoration: none;
}
table {
margin: 0 auto;
border-collapse: collapse;
}
table td {
padding: 3px 20px;
border: 1px var(--table-border-color) solid;
}
table thead {
background: var(--table-header-bg);
}
table thead td {
font-weight: 700;
border: none;
}
table thead th {
padding: 3px 20px;
}
table thead tr {
border: 1px var(--table-header-bg) solid;
}
/* Alternate background colors for rows */
table tbody tr:nth-child(2n) {
background: var(--table-alternate-bg);
}
blockquote {
margin: 20px 0;
padding: 0 20px;
color: var(--fg);
background-color: var(--quote-bg);
border-top: .1em solid var(--quote-border);
border-bottom: .1em solid var(--quote-border);
}
:not(.footnote-definition) + .footnote-definition,
.footnote-definition + :not(.footnote-definition) {
margin-top: 2em;
}
.footnote-definition {
font-size: 0.9em;
margin: 0.5em 0;
}
.footnote-definition p {
display: inline;
}
.tooltiptext {
position: absolute;
visibility: hidden;
color: #fff;
background-color: #333;
transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
left: -8px; /* Half of the width of the icon */
top: -35px;
font-size: 0.8em;
text-align: center;
border-radius: 6px;
padding: 5px 8px;
margin: 5px;
z-index: 1000;
}
.tooltipped .tooltiptext {
visibility: visible;
}

54
docs/css/print.css Normal file
View File

@@ -0,0 +1,54 @@
#sidebar,
#menu-bar,
.nav-chapters,
.mobile-nav-chapters {
display: none;
}
#page-wrapper.page-wrapper {
transform: none;
margin-left: 0px;
overflow-y: initial;
}
#content {
max-width: none;
margin: 0;
padding: 0;
}
.page {
overflow-y: initial;
}
code {
background-color: #666666;
border-radius: 5px;
/* Force background to be printed in Chrome */
-webkit-print-color-adjust: exact;
}
pre > .buttons {
z-index: 2;
}
a, a:visited, a:active, a:hover {
color: #4183c4;
text-decoration: none;
}
h1, h2, h3, h4, h5, h6 {
page-break-inside: avoid;
page-break-after: avoid;
}
pre, code {
page-break-inside: avoid;
white-space: pre-wrap;
}
.fa {
display: none !important;
}

253
docs/css/variables.css Normal file
View File

@@ -0,0 +1,253 @@
/* Globals */
:root {
--sidebar-width: 300px;
--page-padding: 15px;
--content-max-width: 750px;
--menu-bar-height: 50px;
}
/* Themes */
.ayu {
--bg: hsl(210, 25%, 8%);
--fg: #c5c5c5;
--sidebar-bg: #14191f;
--sidebar-fg: #c8c9db;
--sidebar-non-existant: #5c6773;
--sidebar-active: #ffb454;
--sidebar-spacer: #2d334f;
--scrollbar: var(--sidebar-fg);
--icons: #737480;
--icons-hover: #b7b9cc;
--links: #0096cf;
--inline-code-color: #ffb454;
--theme-popup-bg: #14191f;
--theme-popup-border: #5c6773;
--theme-hover: #191f26;
--quote-bg: hsl(226, 15%, 17%);
--quote-border: hsl(226, 15%, 22%);
--table-border-color: hsl(210, 25%, 13%);
--table-header-bg: hsl(210, 25%, 28%);
--table-alternate-bg: hsl(210, 25%, 11%);
--searchbar-border-color: #848484;
--searchbar-bg: #424242;
--searchbar-fg: #fff;
--searchbar-shadow-color: #d4c89f;
--searchresults-header-fg: #666;
--searchresults-border-color: #888;
--searchresults-li-bg: #252932;
--search-mark-bg: #e3b171;
}
.coal {
--bg: hsl(200, 7%, 8%);
--fg: #98a3ad;
--sidebar-bg: #292c2f;
--sidebar-fg: #a1adb8;
--sidebar-non-existant: #505254;
--sidebar-active: #3473ad;
--sidebar-spacer: #393939;
--scrollbar: var(--sidebar-fg);
--icons: #43484d;
--icons-hover: #b3c0cc;
--links: #2b79a2;
--inline-code-color: #c5c8c6;;
--theme-popup-bg: #141617;
--theme-popup-border: #43484d;
--theme-hover: #1f2124;
--quote-bg: hsl(234, 21%, 18%);
--quote-border: hsl(234, 21%, 23%);
--table-border-color: hsl(200, 7%, 13%);
--table-header-bg: hsl(200, 7%, 28%);
--table-alternate-bg: hsl(200, 7%, 11%);
--searchbar-border-color: #aaa;
--searchbar-bg: #b7b7b7;
--searchbar-fg: #000;
--searchbar-shadow-color: #aaa;
--searchresults-header-fg: #666;
--searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f;
--search-mark-bg: #355c7d;
}
.light {
--bg: hsl(0, 0%, 100%);
--fg: #333333;
--sidebar-bg: #fafafa;
--sidebar-fg: #364149;
--sidebar-non-existant: #aaaaaa;
--sidebar-active: #008cff;
--sidebar-spacer: #f4f4f4;
--scrollbar: #cccccc;
--icons: #cccccc;
--icons-hover: #333333;
--links: #4183c4;
--inline-code-color: #6e6b5e;
--theme-popup-bg: #fafafa;
--theme-popup-border: #cccccc;
--theme-hover: #e6e6e6;
--quote-bg: hsl(197, 37%, 96%);
--quote-border: hsl(197, 37%, 91%);
--table-border-color: hsl(0, 0%, 95%);
--table-header-bg: hsl(0, 0%, 80%);
--table-alternate-bg: hsl(0, 0%, 97%);
--searchbar-border-color: #aaa;
--searchbar-bg: #fafafa;
--searchbar-fg: #000;
--searchbar-shadow-color: #aaa;
--searchresults-header-fg: #666;
--searchresults-border-color: #888;
--searchresults-li-bg: #e4f2fe;
--search-mark-bg: #a2cff5;
}
.navy {
--bg: hsl(226, 23%, 11%);
--fg: #bcbdd0;
--sidebar-bg: #282d3f;
--sidebar-fg: #c8c9db;
--sidebar-non-existant: #505274;
--sidebar-active: #2b79a2;
--sidebar-spacer: #2d334f;
--scrollbar: var(--sidebar-fg);
--icons: #737480;
--icons-hover: #b7b9cc;
--links: #2b79a2;
--inline-code-color: #c5c8c6;;
--theme-popup-bg: #161923;
--theme-popup-border: #737480;
--theme-hover: #282e40;
--quote-bg: hsl(226, 15%, 17%);
--quote-border: hsl(226, 15%, 22%);
--table-border-color: hsl(226, 23%, 16%);
--table-header-bg: hsl(226, 23%, 31%);
--table-alternate-bg: hsl(226, 23%, 14%);
--searchbar-border-color: #aaa;
--searchbar-bg: #aeaec6;
--searchbar-fg: #000;
--searchbar-shadow-color: #aaa;
--searchresults-header-fg: #5f5f71;
--searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5;
}
.rust {
--bg: hsl(60, 9%, 87%);
--fg: #262625;
--sidebar-bg: #3b2e2a;
--sidebar-fg: #c8c9db;
--sidebar-non-existant: #505254;
--sidebar-active: #e69f67;
--sidebar-spacer: #45373a;
--scrollbar: var(--sidebar-fg);
--icons: #737480;
--icons-hover: #262625;
--links: #2b79a2;
--inline-code-color: #6e6b5e;
--theme-popup-bg: #e1e1db;
--theme-popup-border: #b38f6b;
--theme-hover: #99908a;
--quote-bg: hsl(60, 5%, 75%);
--quote-border: hsl(60, 5%, 70%);
--table-border-color: hsl(60, 9%, 82%);
--table-header-bg: #b3a497;
--table-alternate-bg: hsl(60, 9%, 84%);
--searchbar-border-color: #aaa;
--searchbar-bg: #fafafa;
--searchbar-fg: #000;
--searchbar-shadow-color: #aaa;
--searchresults-header-fg: #666;
--searchresults-border-color: #888;
--searchresults-li-bg: #dec2a2;
--search-mark-bg: #e69f67;
}
@media (prefers-color-scheme: dark) {
.light.no-js {
--bg: hsl(200, 7%, 8%);
--fg: #98a3ad;
--sidebar-bg: #292c2f;
--sidebar-fg: #a1adb8;
--sidebar-non-existant: #505254;
--sidebar-active: #3473ad;
--sidebar-spacer: #393939;
--scrollbar: var(--sidebar-fg);
--icons: #43484d;
--icons-hover: #b3c0cc;
--links: #2b79a2;
--inline-code-color: #c5c8c6;;
--theme-popup-bg: #141617;
--theme-popup-border: #43484d;
--theme-hover: #1f2124;
--quote-bg: hsl(234, 21%, 18%);
--quote-border: hsl(234, 21%, 23%);
--table-border-color: hsl(200, 7%, 13%);
--table-header-bg: hsl(200, 7%, 28%);
--table-alternate-bg: hsl(200, 7%, 11%);
--searchbar-border-color: #aaa;
--searchbar-bg: #b7b7b7;
--searchbar-fg: #000;
--searchbar-shadow-color: #aaa;
--searchresults-header-fg: #666;
--searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f;
--search-mark-bg: #355c7d;
}
}

10
docs/elasticlunr.min.js vendored Normal file

File diff suppressed because one or more lines are too long

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