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
187 changed files with 35773 additions and 4997 deletions

View File

@@ -9,58 +9,77 @@ jobs:
# specify the version you desire here
- image: circleci/node:11.14.0
working_directory: ~/semaphore/semaphorejs
working_directory: ~/semaphore_private/
steps:
- checkout:
path: ~/semaphore
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 or install npm packages
- restore_cache:
name: restore-npm-cache
keys:
- v1.10-dependencies-{{ checksum "package-lock.json" }}
- v1.9-dependencies-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
paths:
- node_modules
key: v1.10-dependencies-{{ checksum "package-lock.json" }}
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-snarks
command: cd scripts && ./checksum_snarks.sh
name: Checksum snark files
command: cd circuits/ && ./scripts/checksum_snarks.sh
# Download and cache dependencies
- restore_cache:
name: restore-snark-cache
keys:
- v1.13-dependencies-{{ checksum "build/.snark_checksum" }}
- v1.9-dependencies-{{ checksum "circuits/build/.snark_checksum" }}
# build snarks
- run:
name: end-to-end
command: cd scripts && ./build_snarks.sh
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.13-dependencies-{{ checksum "build/.snark_checksum" }}
key: v1.9-dependencies-{{ checksum "circuits/build/.snark_checksum" }}
paths:
- build/circuit.json
- build/proving_key.bin
- build/proving_key.json
- build/verification_key.json
- build/verifier.sol
- circuits/build/circuit.json
- circuits/build/proving_key.bin
- circuits/build/proving_key.json
- circuits/build/verification_key.json
- circuits/build/verifier.sol
# build snarks
- run:
name: integration_tests
command: cd scripts && ./integration_tests.sh
no_output_timeout: 600m
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: ~/semaphore/semaphorejs/build
path: circuits/build

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
docs_src/book/
.etherlime-store
**/build/
circuits/build
contracts/compiled
node_modules
blake2sdef.json
build/.snark_checksum

172
README.md
View File

@@ -1,168 +1,12 @@
# Semaphore
[![CircleCI](https://circleci.com/gh/kobigurk/semaphore.svg?style=svg&circle-token=57fa2a6c591cd8d09ddae610313452bdd7b0fb14)](https://circleci.com/gh/kobigurk/semaphore)
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.
For more information, refer to the
[documentation](https://appliedzkp.github.io/semaphore/).
Join the [Telegram group](https://t.me/joinchat/B-PQx1U3GtAh--Z4Fwo56A) to discuss.
## Introduction
Semaphore has been introduced by [barryWhiteHat](https://github.com/barryWhiteHat) as a method of zero-knowledge signaling - a method for an approved user to broadcast an arbitrary string without exposing their identity. This repository is an implementation of an upgraded version of the concept, including the zero-knowledge circuits and the tools necessary to use it, both server-side and client-side.
The project is implemented in plain Node.JS and uses [circom](https://github.com/iden3/circom) for the zero-knowledge proofs.
## Design
Semaphore is comprised of a zkSNARK statement, a few smart contracts, a server application and a client application.
### Smart contracts
Implemented in [**semaphorejs/contracts**](semaphorejs/contracts).
#### Semaphore
The Semaphore contract is the base layer of Semaphore. Other contracts can build upon this to create applications that rely on anonymous signaling. Semaphore has a tree of allowed identities, a tree of signals, a set of previously broadcast nullifiers hashes, multiple external nullifiers and a gas price refund price:
* The tree of allowed identities allows a prover to show that they are an identity which is approved to signal.
* The tree of signals allows a user to verify the integrity of a list of signals.
* The nullifiers hashes set and external nullifier allows the contract to prevent double signals by the same user, within the context of each external nullifier, without exposing the specific user.
* The gas price refund price is a mechanism that supports transaction abstraction - a server can broadcast on behalf of a user to provide further anonymity, and in return they receive a refund and a small reward, with a maximum gas price for their transaction.
The contract allows administrative operations that only the owner is allowed to perform:
* Managing identities using the **insertIdentity** and **updateIdentity** methods.
* Adding or removing an **external_nullifier**.
* Setting the broadcast permissioning - whether only the owner can broadcast.
The contract allows anyone to read the current state:
* Reading the roots of the two trees.
* Reading the current parameters of **external_nullifier**.
The contract allows anyone to attempt broadcasting a signal, given a signal, a proof and the relevant public inputs.
The contract allows anyone to fund the contract for gas refund and rewards.
Lastly, the contract has a few events to allow a server to build a local state to serve users wishing to generate proofs:
* **Funded** - when the contract has received some funding for refunds and rewards.
* **SignalBroadcast** - when a signal has been broadcast successfully, after verification of the proof, the public inputs and double-signaling checks.
* **LeafAdded**, **LeafUpdated** (from MerkleTreeLib) - when the trees have been updated.
#### MerkleTreeLib
Manages a number of append-only Merkle trees with efficient inserts and updates.
### zkSNARK statement
Implemented in [**semaphorejs/snark**](semaphorejs/snark).
The statement assures that given public inputs:
* **signal_hash**
* **external_nullifier**
* **root**
* **nullifiers_hash**
and private inputs:
* **identity_pk**
* **identity_nullifier**
* **identity_trapdoor**
* **identity_path_elements**
* **identity_path_index**
* **auth_sig_r**
* **auth_sig_s**
the following conditions hold:
* The commitment of the identity structure (**identity_pk**, **identity_nullifier**, **identity_trapdoor**) exists in the identity tree with the root **root**, using the path (**identity_path_elements**, **identity_path_index**). This ensures that the user was added to the system at some point in the past.
* **nullifiers_hash** is uniquely derived from **external_nullifier**, **identity_nullifier** and **identity_path_index**. This ensures a user cannot broadcast a signal with the same **external_nullifier** more than once.
* The message (**external_nullifier**, **signal_hash**) is signed by the secret key corresponding to **identity_pk**, having the signature (**auth_sig_r**, **auth_sig_s**). This ensures that a state of the contract having a specific **external_nullifier**, ensuring no double-signaling.
#### Cryptographic primitives
Semaphore uses a few cryptographic primitives provided by circomlib:
* MiMCHash for the Merkle tree, the identity commitments and the message hash in the signature.
* EdDSA for the signature.
Note: MiMCHash, and especially the specific paramteres used in the circuit, have not been heavily audited yet by the cryptography community. Additionally, the circuit and code should also receive further review before relying on it for production applications.
### Server
Implemented in [**semaphorejs/src/server/server.js**](semaphorejs/src/server/server.js). Acts as a manager of the identities merkle tree and as an identity onboarder. The REST API allows:
* An owner to submit a transaction that adds an identity to the merkle tree, provided proper authentication.
* A client to ask for a path from an identity commitment to the current root of the tree, relieving the client from the need to manage this tree by themselves.
* A client to ask a list of signals, together with their paths to the signals tree root.
* An owner to set the external nullifier.
The server relies on an Ethereum node and the events in the smart contract to synchronize to the current state and handle rollbacks if they occur.
It uses [**semaphore-merkle-tree**](https://github.com/weijiekoh/semaphore-merkle-tree) - Semaphore requires managing a growing merkle tree containing the identities allowed to signal and the signals broadcast by users. semaphore-merkle-tree manages the trees either in-memory, for browser usage or a database, making the tree scale by the disk size.
### Client
Implemented in [**src/client/client.js**](semaphorejs/src/client/client.js). Enables signaling a user's support of an arbitrary statemnt, given identity secrets of an identity existing in the tree. The client has 2 CLI functions:
* **generate_identity** - generate random identity secrets and randomness, save them to disk and print the identity commitment. The client can then send the commitment to the onboarder (using another channel), requesting they add them to the tree.
* **signal STRING** - given an arbitrary string, generates a zero-knowledge proof of the client's authorization to signal. The signalling requests the path of the identity commitment from the server, and broadcasts the transaction directly to the contract.
### Web
A web interface to run the server and client APIs, generate identities and proofs directly in the browser and broadcast signals.
## Running modes
Schematically, Semaphore has the following actors:
![Semaphore architecture](docs/Semaphore.svg)
There are 3 main running modes:
* One server is the owner of the Semaphore contract, other servers act as intermediate miners and clients uses them to broadcast their signals. This the lightest running mode for clients, as they rely on servers to provide them with tree paths, a list of signals and broadcast. This is the default mode that is exposed in the CLI and web clients.
* One server is the owner of the Semaphore contract and clients broadcast directly. This is still a light running mode for clients, and is less prone to server censorship, at the cost of less anonymity - as the user's Ethereum address is exposed.
* One server is the owner of the Semaphore contract and clients run a server locally. This is a heavier running mode, allowing the clients to run autonomously, reconstructing the signals and identities states locally.
## Configuration
The server and the client look for **server-config.json** and **client-config.json**, respectively. They can also accept their configuration as environment variables:
* **server**:
* CONFIG_ENV - load configuration from environment variables rather than a file.
* CONFIG_PATH - location of the configuration file.
* LOG_LEVEL - error, info, debug or verbose.
* DB_PATH - location of the RocksDB database.
* CHAIN_ID - chain ID of the Ethereum network.
* CONTRACT_ADDRESS - the deployed Semaphore contract address.
* CREATION_HASH - the transaction hash in which the contract was created, to allow for faster initial sync.
* NODE_URL - the RPC URL of the Ehtereum node.
* SEMAPHORE_PORT - the port on which to serve the Semaphore server REST API.
* SEMAPHORE_LOGIN - the password with which clients communicating with the Semaphore server REST API must authenticate.
* FROM_ADDRESS - the address to send transactions from.
* FROM_PRIVATE_KEY - the private key of FROM_ADDRESS.
* TRANSACTION_CONFIRMATION_BLOCKS - the amount of blocks to wait until a transaction is considered confirmed. The default is 24.
* **client:**
* CONFIG_ENV - load configuration from environment variables rather than a file.
* LOG_LEVEL - error, info, debug or verbose.
* IDENTITY_PATH - location of the identity secrets file.
* CHAIN_ID - chain ID of the Ethereum network.
* CONTRACT_ADDRESS - the deployed Semaphore contract address.
* NODE_URL - the RPC URL of the Ehtereum node.
* FROM_ADDRESS - the address to send transactions from.
* FROM_PRIVATE_KEY - the private key of FROM_ADDRESS.
* TRANSACTION_CONFIRMATION_BLOCKS - the amount of blocks to wait until a transaction is considered confirmed. The default is 24.
* EXTERNAL_NULLIFIER - the external nullifier to be used with the signal. Must match the one in the contract.
* SEMAPHORE_SERVER_URL - the URL of the Semaphore REST server.
* BROADCASTER_ADDRESS - the address of the Semaphore server that will be allowed to broadcast the client's signals.
## Running
The easiest way to try Semaphore out is to use [https://semaphore.kobi.one](https://semaphore.kobi.one) - a web interface to broadcast to a remote server and generate proofs locally. First, load the Rinkeby config using the button at the top. Then, you can generate an identity and send the commitment to @kobigurk on Telegram or open an issue in the repository. Then, you can broadcast signals, including the proof generation, directly in the browser. Lastly, you can see the signals that have been broadcast to date in the table.
* To try out Semaphore locally you can clone the repository and run the following in `semaphore/semaphorejs/`:
* **npm install && npm link**
* **cd scripts && ./compile.sh && ./do_setup.sh && ./build_verifier.sh** - compile, do a setup and build the verifier of the Semaphore circuit.
* **scripts/run_ganache.sh** - runs ganache with appropriate parameters for Semaphore testing.
* **scripts/run_all_test.sh** - runs a server and a client, generates a new random identity and broadcasts a signal.
It assumes bash, node and truffle are globally available.
Examples of run commands (roughly matching the test contract deployed on Rinkeby):
* `LOG_LEVEL=debug CHAIN_ID=4 CONTRACT_ADDRESS=0x3dE2c3f8853594440c3363f8D491449Defa0bE1F NODE_URL=https://rinkeby.infura.io/v3/f4a3ad81db3f4750bd201955c8d20066 SEMAPHORE_PORT=3000 FROM_ADDRESS=0x1929c15f4e818abf2549510622a50c440c474223 FROM_PRIVATE_KEY=0x6738837df169e8d6ffc6e33a2947e58096d644fa4aa6d74358c8d9d57c12cd21 TRANSACTION_CONFIRMATION_BLOCKS=1 CREATION_HASH=0x4d6998f49f3ebb6e2bd3567c5adbf3f5ab711fbb24e618b4b53498d521f9c758 SEMAPHORE_LOGIN=test123 CONFIG_ENV=true npx semaphorejs-server`
* `LOG_LEVEL=debug TRANSACTION_CONFIRMATION_BLOCKS=1 CHAIN_ID=4 CONTRACT_ADDRESS=0x3dE2c3f8853594440c3363f8D491449Defa0bE1F NODE_URL=https://rinkeby.infura.io/v3/f4a3ad81db3f4750bd201955c8d20066 EXTERNAL_NULLIFIER=12312 SEMAPHORE_SERVER_URL=https://semaphore-server.kobi.one BROADCASTER_ADDRESS=0x1929c15f4e818abf2549510622a50c440c474223 CONFIG_ENV=true npx semaphorejs-client signal "I vote for fork A"`

View File

@@ -345,13 +345,21 @@ template Blake2s(n_bits, personalization) {
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_bits - (n_bits % 512))/8, 1);
compressions[i] = Blake2sCompression(n_rounded_bytes/8, 1);
}
for (var j = 0; j < 32; j++) {
for (var k = 0; k < 8; k++) {

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

@@ -1,4 +1,4 @@
include "../../../snark/blake2s/blake2s.circom";
include "../../../circom/blake2s/blake2s.circom";
template MixingGTester(a, b, c, d) {
signal input in_v[16][32];

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

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.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

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

BIN
docs/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

79
docs/highlight.css Normal file
View File

@@ -0,0 +1,79 @@
/* Base16 Atelier Dune Light - Theme */
/* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) */
/* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */
/* Atelier-Dune Comment */
.hljs-comment,
.hljs-quote {
color: #AAA;
}
/* Atelier-Dune Red */
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-name,
.hljs-regexp,
.hljs-link,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #d73737;
}
/* Atelier-Dune Orange */
.hljs-number,
.hljs-meta,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #b65611;
}
/* Atelier-Dune Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #60ac39;
}
/* Atelier-Dune Blue */
.hljs-title,
.hljs-section {
color: #6684e1;
}
/* Atelier-Dune Purple */
.hljs-keyword,
.hljs-selector-tag {
color: #b854d4;
}
.hljs {
display: block;
overflow-x: auto;
background: #f1f1f1;
color: #6e6b5e;
padding: 0.5em;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-addition {
color: #22863a;
background-color: #f0fff4;
}
.hljs-deletion {
color: #b31d28;
background-color: #ffeef0;
}

2
docs/highlight.js Normal file

File diff suppressed because one or more lines are too long

330
docs/howitworks.html Normal file
View File

@@ -0,0 +1,330 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>How it works - </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" class="active"><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="#how-it-works" id="how-it-works">How it works</a></h1>
<h2><a class="header" href="#inserting-identities" id="inserting-identities">Inserting identities</a></h2>
<p>An identity is comprised of the following information:</p>
<ol>
<li>An <a href="https://en.wikipedia.org/wiki/EdDSA">EdDSA</a> private key. Note that it is
<em>not</em> an Ethereum private key.</li>
<li>An identity nullifier, whih is a random 32-byte value.</li>
<li>An identity trapdoor, whih is a random 32-byte value.</li>
</ol>
<p>An identity commitment is the Pedersen hash of:</p>
<ol>
<li>The public key associated with the identity's private key.</li>
<li>The identity nullifier.</li>
<li>The identity trapdoor.</li>
</ol>
<p>To register an identity, the user must insert their identity commitment into
Semaphore's identity tree. They can do this by calling the Semaphore contract's
<code>insertIdentity(uint256 _identityCommitment)</code> function. See the <a href="./api.html">API
reference</a> for more information.</p>
<h2><a class="header" href="#broadcasting-signals" id="broadcasting-signals">Broadcasting signals</a></h2>
<p>To broadcast a signal, the user must invoke this Semaphore contract function:</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>To zk-SNARK proof must satisfy the constraints created by Semaphore's zk-SNARK
circuit as described below:</p>
<h3><a class="header" href="#the-zk-snark-circuit" id="the-zk-snark-circuit">The zk-SNARK circuit</a></h3>
<p>The <a href="./circuits/circom/semaphore-base.circom">semaphore-base.circom</a> circuit
helps to prove the following:</p>
<h3><a class="header" href="#that-the-identity-commitment-exists-in-the-merkle-tree" id="that-the-identity-commitment-exists-in-the-merkle-tree">That the identity commitment exists in the Merkle tree</a></h3>
<p><strong>Private inputs:</strong></p>
<ul>
<li><code>identity_pk</code>: the user's EdDSA public key</li>
<li><code>identity_nullifier</code>: a random 32-byte value which the user should save</li>
<li><code>identity_trapdoor</code>: a random 32-byte value which the user should save</li>
<li><code>identity_path_elements</code>: the values along the Merkle path to the
user's identity commitment</li>
<li><code>identity_path_index[n_levels]</code>: the direction (left/right) per tree level
corresponding to the Merkle path to the user's identity commitment</li>
</ul>
<p><strong>Public inputs:</strong></p>
<ul>
<li><code>root</code>: The Merkle root of the identity tree</li>
</ul>
<p><strong>Procedure:</strong></p>
<p>The circuit hashes the public key, identity nullifier, and identity trapdoor to
generate an <strong>identity commitment</strong>. It then verifies the Merkle proof against
the Merkle root and the identity commitment.</p>
<h3><a class="header" href="#that-the-signal-was-only-broadcasted-once" id="that-the-signal-was-only-broadcasted-once">That the signal was only broadcasted once</a></h3>
<p><strong>Private inputs:</strong></p>
<ul>
<li><code>identity_nullifier</code>: as above</li>
<li><code>identity_path_index</code>: as above</li>
</ul>
<p><strong>Public inputs:</strong></p>
<ul>
<li><code>external_nullifier</code>: the 29-byte external nullifier - see above</li>
<li><code>nullifiers_hash</code>: the hash of the identity nullifier, external nullifier,
and Merkle path index (<code>identity_path_index</code>)</li>
</ul>
<p><strong>Procedure:</strong></p>
<p>The circuit hashes the given identity nullifier, external nullifier, and Merkle
path index, and checks that it matches the given nullifiers hash. Additionally,
the smart contract ensures that it has not previously seen this nullifiers
hash. This way, double-signalling is impossible.</p>
<h3><a class="header" href="#that-the-signal-was-truly-broadcasted-by-the-user-who-generated-the-proof" id="that-the-signal-was-truly-broadcasted-by-the-user-who-generated-the-proof">That the signal was truly broadcasted by the user who generated the proof</a></h3>
<p><strong>Private inputs:</strong></p>
<ul>
<li><code>identity_pk</code>: as above</li>
<li><code>auth_sig_r</code>: the <code>r</code> value of the signature of the signal</li>
<li><code>auth_sig_s</code>: the <code>s</code> value of the signature of the signal</li>
</ul>
<p><strong>Public inputs:</strong></p>
<ul>
<li><code>signal_hash</code>: the hash of the signal</li>
<li><code>external_nullifier</code>: the 29-byte external nullifier - see above</li>
</ul>
<p><strong>Procedure:</strong></p>
<p>The circuit hashes the signal hash and the external nullifier, and verifies
this output against the given public key and signature. This ensures the
authenticity of the signal and prevents front-running attacks.</p>
<h2><a class="header" href="#cryptographic-primitives" id="cryptographic-primitives">Cryptographic primitives</a></h2>
<p>Semaphore uses MiMC for the Merkle tree, Pedersen commmitments for the identity
commitments, Blake2 for the nullifiers hash, and EdDSA for the signature.</p>
<p>MiMC is a relatively new hash function. We use the recommended MiMC
construction from <a href="https://eprint.iacr.org/2016/492.pdf">Albrecht et al</a>, and
there is a prize to break MiMC at <a href="http://mimchash.org">http://mimchash.org</a>
which has not been claimed yet.</p>
<p>We have also implemented a version of Semaphore which uses the Poseidon hash
function for the Merkle tree and EdDSA signature verification. This may have
better security than MiMC, allows identity insertions to save about 20% gas,
and roughly halves the proving time. Note, however, that the Poseidon-related
circuits and EVM bytecode generator have not been audited, so use it with
caution. To use it, checkout the <code>feat/poseidon</code> branch of this repository.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="about.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="quickstart.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="about.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="quickstart.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>

293
docs/index.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"><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>

382
docs/libsemaphore.html Normal file
View File

@@ -0,0 +1,382 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>libsemaphore - </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" class="active"><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="#libsemaphore" id="libsemaphore">libsemaphore</a></h1>
<p><a href="https://www.npmjs.com/package/libsemaphore"><code>libsemaphore</code></a> is a helper
library for Semaphore written in Typescript. Any dApp written in Javascript or
Typescript should use it as it provides useful abstractions over common tasks
and objects, such as identities and proof generation.</p>
<p>Note that only v1.0.14 and above works with the Semaphore code in this
repository. v0.0.x is compatible with the pre-audited Semaphore code.</p>
<h2><a class="header" href="#available-types-interfaces-and-functions" id="available-types-interfaces-and-functions">Available types, interfaces, and functions</a></h2>
<h3><a class="header" href="#types" id="types">Types</a></h3>
<p><strong><code>SnarkBigInt</code></strong></p>
<p>A big integer type compatible with the <code>snarkjs</code> library. Note that it is not
advisable to mix variables of this type with <code>bigNumber</code>s or <code>BigInt</code>s.
Encapsulates <code>snarkjs.bigInt</code>.</p>
<p><strong><code>EddsaPrivateKey</code></strong></p>
<p>An <a href="https://tools.ietf.org/html/rfc8032">EdDSA</a> private key which should be 32
bytes long. Encapsulates a <a href="https://nodejs.org/api/buffer.html"><code>Buffer</code></a>.</p>
<p><strong><code>EddsaPublicKey</code></strong></p>
<p>An EdDSA public key. Encapsulates an array of <code>SnarkBigInt</code>s.</p>
<p><strong><code>SnarkProvingKey</code></strong></p>
<p>A proving key, which when used with a secret <em>witness</em>, generates a zk-SNARK
proof about said witness. Encapsulates a <code>Buffer</code>.</p>
<p><strong><code>SnarkVerifyingKey</code></strong></p>
<p>A verifying key which when used with public inputs to a zk-SNARK and a
<code>SnarkProof</code>, can prove the proof's validity. Encapsulates a <code>Buffer</code>.</p>
<p><strong><code>SnarkWitness</code></strong></p>
<p>The secret inputs to a zk-SNARK. Encapsulates an array of <code>SnarkBigInt</code>s.</p>
<p><strong><code>SnarkPublicSignals</code></strong></p>
<p>The public inputs to a zk-SNARK. Encapsulates an array of <code>SnarkBigInt</code>s.</p>
<h3><a class="header" href="#interfaces" id="interfaces">Interfaces</a></h3>
<p><strong><code>EddsaKeyPair</code></strong></p>
<p>Encapsulates an <code>EddsaPublicKey</code> and an <code>EddsaPrivateKey</code>.</p>
<pre><code class="language-ts">interface EddsaKeyPair {
pubKey: EddsaPublicKey,
privKey: EddsaPrivateKey,
}
</code></pre>
<p><strong><code>Identity</code></strong></p>
<p>Encapsulates all information required to generate an identity commitment, and
is crucial to creating <code>SnarkProof</code>s to broadcast signals.</p>
<pre><code class="language-ts">interface Identity {
keypair: EddsaKeyPair,
identityNullifier: SnarkBigInt,
identityTrapdoor: SnarkBigInt,
}
</code></pre>
<p><strong><code>SnarkProof</code></strong></p>
<p>Note that <code>broadcastSignal()</code> accepts a <code>uint256[8]</code> array for its <code>_proof</code>
parameter. See <code>genBroadcastSignalParams()</code>.</p>
<pre><code class="language-ts">interface SnarkProof {
pi_a: SnarkBigInt[]
pi_b: SnarkBigInt[][]
pi_c: SnarkBigInt[]
}
</code></pre>
<h3><a class="header" href="#functions" id="functions">Functions</a></h3>
<p><strong><code>genPubKey(privKey: EddsaPrivateKey): EddsaPublicKey</code></strong></p>
<p>Generates a public EdDSA key from a supplied private key. To generate a private
key, use <code>crypto.randomBytes(32)</code> where <code>crypto</code> is the built-in Node or
browser module.</p>
<p><strong><code>genIdentity(): Identity</code></strong></p>
<p>This is a convenience function to generate a fresh and random <code>Identity</code>. That
is, the 32-byte private key for the <code>EddsaKeyPair</code> is randomly generated, as
are the distinct 31-byte identity nullifier and the 31-byte identity trapdoor
values.</p>
<p><strong><code>serialiseIdentity(identity: Identity): string</code></strong></p>
<p>Converts an <code>Identity</code> into a JSON string which looks like this:</p>
<pre><code class="language-text">[&quot;e82cc2b8654705e427df423c6300307a873a2e637028fab3163cf95b18bb172e&quot;,&quot;a02e517dfb3a4184adaa951d02bfe0fe092d1ee34438721d798db75b8db083&quot;,&quot;15c6540bf7bddb0616984fccda7e954a0fb5ea4679ac686509dc4bd7ba9c3b&quot;]
</code></pre>
<p>You can also spell this function as <code>serializeIdentity</code>.</p>
<p>To convert this string back into an <code>Identity</code>, use <code>unSerialiseIdentity()</code>.</p>
<p><strong><code>unSerialiseIdentity(string: serialisedId): Identity</code></strong></p>
<p>Converts the <code>string</code> output of <code>serialiseIdentity()</code> to an <code>Identity</code>.</p>
<p>You can also spell this function as <code>unSerializeIdentity</code>.</p>
<p><strong><code>genIdentityCommitment(identity: Identity): SnarkBigInt</code></strong></p>
<p>Generates an identity commitment, which is the hash of the public key, the
identity nullifier, and the identity trapdoor.</p>
<p><strong><code>async genProof(witness: SnarkWitness, provingKey: SnarkProvingKey): SnarkProof</code></strong></p>
<p>Generates a <code>SnarkProof</code>, which can be sent to the Semaphore contract's
<code>broadcastSignal()</code> function. It can also be verified off-chain using
<code>verifyProof()</code> below.</p>
<p><strong><code>genPublicSignals(witness: SnarkWitness, circuit: SnarkCircuit): SnarkPublicSignals</code></strong></p>
<p>Extracts the public signals to be supplied to the contract or <code>verifyProof()</code>.</p>
<p><strong><code>verifyProof(verifyingKey: SnarkVerifyingKey, proof: SnarkProof, publicSignals: SnarkPublicSignals): boolean</code></strong></p>
<p>Returns <code>true</code> if the given <code>proof</code> is valid, given the correct verifying key
and public signals.</p>
<p>Returns <code>false</code> otherwise.</p>
<p><strong><code>signMsg(privKey: EddsaPrivateKey, msg: SnarkBigInt): EdDSAMiMcSpongeSignature)</code></strong></p>
<p>Encapsualtes <code>circomlib.eddsa.signMiMCSponge</code> to sign a message <code>msg</code> using private key <code>privKey</code>.</p>
<p><strong><code>verifySignature(msg: SnarkBigInt, signature: EdDSAMiMcSpongeSignature, pubKey: EddsaPublicKey)</code>: boolean</strong></p>
<p>Returns <code>true</code> if the cryptographic <code>signature</code> of the signed <code>msg</code> is from the
private key associated with <code>pubKey</code>.</p>
<p>Returns <code>false</code> otherwise.</p>
<p><strong><code>setupTree(levels: number, prefix: string): MerkleTree</code></strong></p>
<p>Returns a Merkle tree created using
<a href="https://www.npmjs.com/package/semaphore-merkle-tree"><code>semaphore-merkle-tree</code></a>
with the same number of levels which the Semaphore zk-SNARK circuit expects.
This tree is also configured to use <code>MimcSpongeHasher</code>, which is also what the
circuit expects.</p>
<p><code>levels</code> sets the number of levels of the tree. A tree with 20 levels, for
instance, supports up to 1048576 deposits.</p>
<p><strong><code>genCircuit(circuitDefinition: any)</code></strong></p>
<p>Returns a <code>new snarkjs.Circuit(circuitDefinition)</code>. The <code>circuitDefinition</code>
object should be the <code>JSON.parse</code>d result of the <code>circom</code> command which
converts a <code>.circom</code> file to a <code>.json</code> file.</p>
<p><strong><code>async genWitness(...)</code></strong></p>
<p>This function has the following signature:</p>
<pre><code class="language-ts">const genWitness = async (
signal: string,
circuit: SnarkCircuit,
identity: Identity,
idCommitments: SnarkBigInt[] | BigInt[] | ethers.utils.BigNumber[],
treeDepth: number,
externalNullifier: SnarkBigInt,
)
</code></pre>
<ul>
<li><code>signal</code> is the string you wish to broadcast.</li>
<li><code>circuit</code> is the output of <code>genCircuit()</code>.</li>
<li><code>identity</code> is the <code>Identity</code> whose identity commitment you want to prove is
in the set of registered identities.</li>
<li><code>idCommitments</code> is an array of registered identity commmitments; i.e. the
leaves of the tree.</li>
<li><code>treeDepth</code> is the number of levels which the Merkle tree used has</li>
<li><code>externalNullifier</code> is the current external nullifier</li>
</ul>
<p>It returns an object as such:</p>
<ul>
<li><code>witness</code>: The witness to pass to <code>genProof()</code>.</li>
<li><code>signal</code>: The computed signal for Semaphore. This is the hash of the
recipient's address, relayer's address, and fee.</li>
<li><code>signalHash</code>: The hash of the computed signal.</li>
<li><code>msg</code>: The hash of the external nullifier and the signal hash</li>
<li><code>signature</code>: The signature on the above msg.</li>
<li><code>tree</code>: The Merkle tree object after it has been updated with the identity commitment</li>
<li><code>identityPath</code>: The Merkle path to the identity commmitment</li>
<li><code>identityPathIndex</code>: The leaf index of the identity commitment</li>
<li><code>identityPathElements</code>: The elements along the above Merkle path</li>
</ul>
<p>Only <code>witness</code> is essential to generate the proof; the other data is only
useful for debugging and additional off-chain checks, such as verifying the
signature and the Merkle tree root.</p>
<p><strong><code>formatForVerifierContract = (proof: SnarkProof, publicSignals: SnarkPublicSignals</code></strong></p>
<p>Converts the data in <code>proof</code> and <code>publicSignals</code> to strings and rearranges
elements of <code>proof.pi_b</code> so that <code>snarkjs</code>'s <code>verifier.sol</code> will accept it.
To be specific, it returns an object as such:</p>
<pre><code class="language-ts">{
a: [ proof.pi_a[0].toString(), proof.pi_a[1].toString() ],
b: [
[ proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString() ],
[ proof.pi_b[1][1].toString(), proof.pi_b[1][0].toString() ],
],
c: [ proof.pi_c[0].toString(), proof.pi_c[1].toString() ],
input: publicSignals.map((x) =&gt; x.toString()),
}
</code></pre>
<p><strong><code>stringifyBigInts = (obj: any) =&gt; object</code></strong></p>
<p>Encapsulates <code>snarkjs.stringifyBigInts()</code>. Makes it easy to convert <code>SnarkProof</code>s to JSON. </p>
<p><strong><code>unstringifyBigInts = (obj: any) =&gt; object</code></strong></p>
<p>Encapsulates <code>snarkjs.unstringifyBigInts()</code>. Makes it easy to convert JSON to <code>SnarkProof</code>s.</p>
<p><strong><code>genExternalNullifier = (plaintext: string) =&gt; string</code></strong></p>
<p>Each external nullifier must be at most 29 bytes large. This function
keccak-256-hashes a given <code>plaintext</code>, takes the last 29 bytes, and pads it
(from the start) with 0s, and returns the resulting hex string.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="api.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="trustedsetup.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="api.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="trustedsetup.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>

7
docs/mark.min.js vendored Normal file

File diff suppressed because one or more lines are too long

823
docs/print.html Normal file
View File

@@ -0,0 +1,823 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title></title>
<meta name="robots" content="noindex" />
<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"><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>
<h1><a class="header" href="#how-it-works" id="how-it-works">How it works</a></h1>
<h2><a class="header" href="#inserting-identities" id="inserting-identities">Inserting identities</a></h2>
<p>An identity is comprised of the following information:</p>
<ol>
<li>An <a href="https://en.wikipedia.org/wiki/EdDSA">EdDSA</a> private key. Note that it is
<em>not</em> an Ethereum private key.</li>
<li>An identity nullifier, whih is a random 32-byte value.</li>
<li>An identity trapdoor, whih is a random 32-byte value.</li>
</ol>
<p>An identity commitment is the Pedersen hash of:</p>
<ol>
<li>The public key associated with the identity's private key.</li>
<li>The identity nullifier.</li>
<li>The identity trapdoor.</li>
</ol>
<p>To register an identity, the user must insert their identity commitment into
Semaphore's identity tree. They can do this by calling the Semaphore contract's
<code>insertIdentity(uint256 _identityCommitment)</code> function. See the <a href="./api.html">API
reference</a> for more information.</p>
<h2><a class="header" href="#broadcasting-signals" id="broadcasting-signals">Broadcasting signals</a></h2>
<p>To broadcast a signal, the user must invoke this Semaphore contract function:</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>To zk-SNARK proof must satisfy the constraints created by Semaphore's zk-SNARK
circuit as described below:</p>
<h3><a class="header" href="#the-zk-snark-circuit" id="the-zk-snark-circuit">The zk-SNARK circuit</a></h3>
<p>The <a href="./circuits/circom/semaphore-base.circom">semaphore-base.circom</a> circuit
helps to prove the following:</p>
<h3><a class="header" href="#that-the-identity-commitment-exists-in-the-merkle-tree" id="that-the-identity-commitment-exists-in-the-merkle-tree">That the identity commitment exists in the Merkle tree</a></h3>
<p><strong>Private inputs:</strong></p>
<ul>
<li><code>identity_pk</code>: the user's EdDSA public key</li>
<li><code>identity_nullifier</code>: a random 32-byte value which the user should save</li>
<li><code>identity_trapdoor</code>: a random 32-byte value which the user should save</li>
<li><code>identity_path_elements</code>: the values along the Merkle path to the
user's identity commitment</li>
<li><code>identity_path_index[n_levels]</code>: the direction (left/right) per tree level
corresponding to the Merkle path to the user's identity commitment</li>
</ul>
<p><strong>Public inputs:</strong></p>
<ul>
<li><code>root</code>: The Merkle root of the identity tree</li>
</ul>
<p><strong>Procedure:</strong></p>
<p>The circuit hashes the public key, identity nullifier, and identity trapdoor to
generate an <strong>identity commitment</strong>. It then verifies the Merkle proof against
the Merkle root and the identity commitment.</p>
<h3><a class="header" href="#that-the-signal-was-only-broadcasted-once" id="that-the-signal-was-only-broadcasted-once">That the signal was only broadcasted once</a></h3>
<p><strong>Private inputs:</strong></p>
<ul>
<li><code>identity_nullifier</code>: as above</li>
<li><code>identity_path_index</code>: as above</li>
</ul>
<p><strong>Public inputs:</strong></p>
<ul>
<li><code>external_nullifier</code>: the 29-byte external nullifier - see above</li>
<li><code>nullifiers_hash</code>: the hash of the identity nullifier, external nullifier,
and Merkle path index (<code>identity_path_index</code>)</li>
</ul>
<p><strong>Procedure:</strong></p>
<p>The circuit hashes the given identity nullifier, external nullifier, and Merkle
path index, and checks that it matches the given nullifiers hash. Additionally,
the smart contract ensures that it has not previously seen this nullifiers
hash. This way, double-signalling is impossible.</p>
<h3><a class="header" href="#that-the-signal-was-truly-broadcasted-by-the-user-who-generated-the-proof" id="that-the-signal-was-truly-broadcasted-by-the-user-who-generated-the-proof">That the signal was truly broadcasted by the user who generated the proof</a></h3>
<p><strong>Private inputs:</strong></p>
<ul>
<li><code>identity_pk</code>: as above</li>
<li><code>auth_sig_r</code>: the <code>r</code> value of the signature of the signal</li>
<li><code>auth_sig_s</code>: the <code>s</code> value of the signature of the signal</li>
</ul>
<p><strong>Public inputs:</strong></p>
<ul>
<li><code>signal_hash</code>: the hash of the signal</li>
<li><code>external_nullifier</code>: the 29-byte external nullifier - see above</li>
</ul>
<p><strong>Procedure:</strong></p>
<p>The circuit hashes the signal hash and the external nullifier, and verifies
this output against the given public key and signature. This ensures the
authenticity of the signal and prevents front-running attacks.</p>
<h2><a class="header" href="#cryptographic-primitives" id="cryptographic-primitives">Cryptographic primitives</a></h2>
<p>Semaphore uses MiMC for the Merkle tree, Pedersen commmitments for the identity
commitments, Blake2 for the nullifiers hash, and EdDSA for the signature.</p>
<p>MiMC is a relatively new hash function. We use the recommended MiMC
construction from <a href="https://eprint.iacr.org/2016/492.pdf">Albrecht et al</a>, and
there is a prize to break MiMC at <a href="http://mimchash.org">http://mimchash.org</a>
which has not been claimed yet.</p>
<p>We have also implemented a version of Semaphore which uses the Poseidon hash
function for the Merkle tree and EdDSA signature verification. This may have
better security than MiMC, allows identity insertions to save about 20% gas,
and roughly halves the proving time. Note, however, that the Poseidon-related
circuits and EVM bytecode generator have not been audited, so use it with
caution. To use it, checkout the <code>feat/poseidon</code> branch of this repository.</p>
<h1><a class="header" href="#quick-start" id="quick-start">Quick start</a></h1>
<p>Semaphore has been tested with Node 11.14.0 and Node 12 LTE. Use
<a href="https://github.com/nvm-sh/nvm"><code>nvm</code></a> to manage your Node version.</p>
<p>Clone this repository, install dependencies, and build the source code:</p>
<pre><code class="language-bash">git clone git@github.com:kobigurk/semaphore.git &amp;&amp; \
cd semaphore &amp;&amp; \
npm i &amp;&amp; \
npm run bootstrap &amp;&amp; \
npm run build
</code></pre>
<p>Next, either download the compiled zk-SNARK circuit, proving key, and
verification key (note that these keys are for testing purposes, and not for
production, as there is no certainty that the toxic waste was securely
discarded).</p>
<p>To download the circuit, proving key, and verification key, run:</p>
<pre><code class="language-bash"># Start from the base directory
cd circuits &amp;&amp; \
./circuits/scripts/download_snarks.sh
</code></pre>
<p>To generate the above files locally instead, run:</p>
<pre><code class="language-bash"># Start from the base directory
cd circuits &amp;&amp; \
./circuits/scripts/build_snarks.sh
</code></pre>
<p>This process should take about 45 minutes.</p>
<p>Build the Solidity contracts (you need <code>solc</code> v 0.5.12 installed in your
<code>$PATH</code>):</p>
<pre><code class="language-bash"># Start from the base directory
cd contracts &amp;&amp; \
npm run compileSol
</code></pre>
<p>Run tests while still in the <code>contracts/</code> directory:</p>
<pre><code class="language-bash"># The first command tests the Merkle tree contract and the second
# tests the Semaphore contract
npm run test-semaphore &amp;&amp; \
npm run test-mt
</code></pre>
<h1><a class="header" href="#usage" id="usage">Usage</a></h1>
<p>The Semaphore contract forms a base layer for other contracts to create
applications that rely on anonymous signaling.</p>
<p>First, you should ensure that the proving key, verification key, and circuit
file, which are static, be easily available to your users. These may be hosted
in a CDN or bundled with your application code.</p>
<p>The Semaphore team has not performed a trusted setup yet, so trustworthy
versions of these files are not available yet.</p>
<p>Untrusted versions of these files, however, may be obtained via the
<code>circuits/scripts/download_snarks.sh</code> script.</p>
<p>Next, to have full flexibility over Semaphore's mechanisms, write a Client
contract and set the owner of the Semaphore contract as the address of the
Client contract. You may also write a Client contract which deploys a Semaphore
contract in its constructor, or on the fly. </p>
<p>With the Client contract as the owner of the Semaphore contract, the Client
contract may call owner-only Semaphore functions such as
<code>addExternalNullifier()</code>.</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>These functions add, deactivate, and reactivate an external nullifier respectively.
As each identity can only signal once to an external nullifier, and as a signal
can only be successfully broadcasted to an active external nullifier, these
functions enable use cases where it is necessary to have multiple external
nullifiers or to activate and/or deactivate them.</p>
<p>Refer to the <a href="https://medium.com/coinmonks/to-mixers-and-beyond-presenting-semaphore-a-privacy-gadget-built-on-ethereum-4c8b00857c9b">high-level explanation of
Semaphore</a>
for more details.</p>
<h2><a class="header" href="#set-broadcast-permissioning" id="set-broadcast-permissioning">Set broadcast permissioning</a></h2>
<p>Note that <code>Semaphore.broadcastSignal()</code> is permissioned by default, so if you
wish for anyone to be able to broadcast a signal, the owner of the Semaphore
contract (either a Client contract or externally owned account) must first
invoke <code>setPermissioning(false)</code>.</p>
<p>See <a href="https://github.com/appliedzkp/semaphore/blob/master/contracts/sol/SemaphoreClient.sol">SemaphoreClient.sol</a> for an example.</p>
<h2><a class="header" href="#insert-identities" id="insert-identities">Insert identities</a></h2>
<p>To generate an identity commitment, use the <code>libsemaphore</code> functions
<code>genIdentity()</code> and <code>genIdentityCommitment()</code> Typescript (or Javascript)
functions:</p>
<pre><code class="language-ts">const identity: Identity = genIdentity()
const identityCommitment = genIdentityCommitment(identity)
</code></pre>
<p>Be sure to store <code>identity</code> somewhere safe. The <code>serialiseIdentity()</code> function
can help with this:</p>
<p><code>const serialisedId: string = serialiseIdentity(identity: Identity)</code></p>
<p>It converts an <code>Identity</code> into a JSON string which looks like this:</p>
<pre><code class="language-text">[&quot;e82cc2b8654705e427df423c6300307a873a2e637028fab3163cf95b18bb172e&quot;,&quot;a02e517dfb3a4184adaa951d02bfe0fe092d1ee34438721d798db75b8db083&quot;,&quot;15c6540bf7bddb0616984fccda7e954a0fb5ea4679ac686509dc4bd7ba9c3b&quot;]
</code></pre>
<p>To convert this string back into an <code>Identity</code>, use <code>unSerialiseIdentity()</code>.</p>
<p><code>const id: Identity = unSerialiseIdentity(serialisedId)</code></p>
<h2><a class="header" href="#broadcast-signals" id="broadcast-signals">Broadcast signals</a></h2>
<p>First obtain the leaves of the identity tree (in sequence, up to the user's
identity commitment, or more).</p>
<pre><code class="language-ts">const leaves = &lt;list of leaves&gt;
</code></pre>
<p>Next, load the circuit from disk (or from a remote source):</p>
<pre><code class="language-ts">const circuitPath = path.join(__dirname, '/path/to/circuit.json')
const cirDef = JSON.parse(fs.readFileSync(circuitPath).toString())
const circuit = genCircuit(cirDef)
</code></pre>
<p>Next, use <code>libsemaphore</code>'s <code>genWitness()</code> helper function as such:</p>
<pre><code>const result = await genWitness(
signal,
circuit,
identity,
leaves,
num_levels,
external_nullifier,
)
</code></pre>
<ul>
<li><code>signal</code>: a string which is the signal to broadcast.</li>
<li><code>circuit</code>: the output of <code>genCircuit()</code> (see above).</li>
<li><code>identity</code>: the user's identity as an <code>Identity</code> object.</li>
<li><code>leaves</code> the list of leaves in the tree (see above).</li>
<li><code>num_levels</code>: the depth of the Merkle tree.</li>
<li><code>external_nullifier</code>: the external nullifier at which to broadcast.</li>
</ul>
<p>Load the proving key from disk (or from a remote source):</p>
<pre><code class="language-ts">const provingKeyPath = path.join(__dirname, '/path/to/proving_key.bin')
const provingKey: SnarkProvingKey = fs.readFileSync(provingKeyPath)
</code></pre>
<p>Generate the proof (this takes about 30-45 seconds on a modern laptop):</p>
<pre><code class="language-ts">const proof = await genProof(result.witness, provingKey)
</code></pre>
<p>Generate the <code>broadcastSignal()</code> parameters:</p>
<pre><code class="language-ts">const publicSignals = genPublicSignals(result.witness, circuit)
const params = genBroadcastSignalParams(result, proof, publicSignals)
</code></pre>
<p>Finally, invoke <code>broadcastSignal()</code> with the parameters:</p>
<pre><code class="language-ts">const tx = await semaphoreClientContract.broadcastSignal(
ethers.utils.toUtf8Bytes(signal),
params.proof,
params.root,
params.nullifiersHash,
external_nullifier,
{ gasLimit: 500000 },
)
</code></pre>
<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-1" id="add-deactivate-or-reactivate-external-nullifiiers-1">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-1" id="insert-identities-1">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-1" id="broadcast-signals-1">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>
<h1><a class="header" href="#libsemaphore" id="libsemaphore">libsemaphore</a></h1>
<p><a href="https://www.npmjs.com/package/libsemaphore"><code>libsemaphore</code></a> is a helper
library for Semaphore written in Typescript. Any dApp written in Javascript or
Typescript should use it as it provides useful abstractions over common tasks
and objects, such as identities and proof generation.</p>
<p>Note that only v1.0.14 and above works with the Semaphore code in this
repository. v0.0.x is compatible with the pre-audited Semaphore code.</p>
<h2><a class="header" href="#available-types-interfaces-and-functions" id="available-types-interfaces-and-functions">Available types, interfaces, and functions</a></h2>
<h3><a class="header" href="#types" id="types">Types</a></h3>
<p><strong><code>SnarkBigInt</code></strong></p>
<p>A big integer type compatible with the <code>snarkjs</code> library. Note that it is not
advisable to mix variables of this type with <code>bigNumber</code>s or <code>BigInt</code>s.
Encapsulates <code>snarkjs.bigInt</code>.</p>
<p><strong><code>EddsaPrivateKey</code></strong></p>
<p>An <a href="https://tools.ietf.org/html/rfc8032">EdDSA</a> private key which should be 32
bytes long. Encapsulates a <a href="https://nodejs.org/api/buffer.html"><code>Buffer</code></a>.</p>
<p><strong><code>EddsaPublicKey</code></strong></p>
<p>An EdDSA public key. Encapsulates an array of <code>SnarkBigInt</code>s.</p>
<p><strong><code>SnarkProvingKey</code></strong></p>
<p>A proving key, which when used with a secret <em>witness</em>, generates a zk-SNARK
proof about said witness. Encapsulates a <code>Buffer</code>.</p>
<p><strong><code>SnarkVerifyingKey</code></strong></p>
<p>A verifying key which when used with public inputs to a zk-SNARK and a
<code>SnarkProof</code>, can prove the proof's validity. Encapsulates a <code>Buffer</code>.</p>
<p><strong><code>SnarkWitness</code></strong></p>
<p>The secret inputs to a zk-SNARK. Encapsulates an array of <code>SnarkBigInt</code>s.</p>
<p><strong><code>SnarkPublicSignals</code></strong></p>
<p>The public inputs to a zk-SNARK. Encapsulates an array of <code>SnarkBigInt</code>s.</p>
<h3><a class="header" href="#interfaces" id="interfaces">Interfaces</a></h3>
<p><strong><code>EddsaKeyPair</code></strong></p>
<p>Encapsulates an <code>EddsaPublicKey</code> and an <code>EddsaPrivateKey</code>.</p>
<pre><code class="language-ts">interface EddsaKeyPair {
pubKey: EddsaPublicKey,
privKey: EddsaPrivateKey,
}
</code></pre>
<p><strong><code>Identity</code></strong></p>
<p>Encapsulates all information required to generate an identity commitment, and
is crucial to creating <code>SnarkProof</code>s to broadcast signals.</p>
<pre><code class="language-ts">interface Identity {
keypair: EddsaKeyPair,
identityNullifier: SnarkBigInt,
identityTrapdoor: SnarkBigInt,
}
</code></pre>
<p><strong><code>SnarkProof</code></strong></p>
<p>Note that <code>broadcastSignal()</code> accepts a <code>uint256[8]</code> array for its <code>_proof</code>
parameter. See <code>genBroadcastSignalParams()</code>.</p>
<pre><code class="language-ts">interface SnarkProof {
pi_a: SnarkBigInt[]
pi_b: SnarkBigInt[][]
pi_c: SnarkBigInt[]
}
</code></pre>
<h3><a class="header" href="#functions" id="functions">Functions</a></h3>
<p><strong><code>genPubKey(privKey: EddsaPrivateKey): EddsaPublicKey</code></strong></p>
<p>Generates a public EdDSA key from a supplied private key. To generate a private
key, use <code>crypto.randomBytes(32)</code> where <code>crypto</code> is the built-in Node or
browser module.</p>
<p><strong><code>genIdentity(): Identity</code></strong></p>
<p>This is a convenience function to generate a fresh and random <code>Identity</code>. That
is, the 32-byte private key for the <code>EddsaKeyPair</code> is randomly generated, as
are the distinct 31-byte identity nullifier and the 31-byte identity trapdoor
values.</p>
<p><strong><code>serialiseIdentity(identity: Identity): string</code></strong></p>
<p>Converts an <code>Identity</code> into a JSON string which looks like this:</p>
<pre><code class="language-text">[&quot;e82cc2b8654705e427df423c6300307a873a2e637028fab3163cf95b18bb172e&quot;,&quot;a02e517dfb3a4184adaa951d02bfe0fe092d1ee34438721d798db75b8db083&quot;,&quot;15c6540bf7bddb0616984fccda7e954a0fb5ea4679ac686509dc4bd7ba9c3b&quot;]
</code></pre>
<p>You can also spell this function as <code>serializeIdentity</code>.</p>
<p>To convert this string back into an <code>Identity</code>, use <code>unSerialiseIdentity()</code>.</p>
<p><strong><code>unSerialiseIdentity(string: serialisedId): Identity</code></strong></p>
<p>Converts the <code>string</code> output of <code>serialiseIdentity()</code> to an <code>Identity</code>.</p>
<p>You can also spell this function as <code>unSerializeIdentity</code>.</p>
<p><strong><code>genIdentityCommitment(identity: Identity): SnarkBigInt</code></strong></p>
<p>Generates an identity commitment, which is the hash of the public key, the
identity nullifier, and the identity trapdoor.</p>
<p><strong><code>async genProof(witness: SnarkWitness, provingKey: SnarkProvingKey): SnarkProof</code></strong></p>
<p>Generates a <code>SnarkProof</code>, which can be sent to the Semaphore contract's
<code>broadcastSignal()</code> function. It can also be verified off-chain using
<code>verifyProof()</code> below.</p>
<p><strong><code>genPublicSignals(witness: SnarkWitness, circuit: SnarkCircuit): SnarkPublicSignals</code></strong></p>
<p>Extracts the public signals to be supplied to the contract or <code>verifyProof()</code>.</p>
<p><strong><code>verifyProof(verifyingKey: SnarkVerifyingKey, proof: SnarkProof, publicSignals: SnarkPublicSignals): boolean</code></strong></p>
<p>Returns <code>true</code> if the given <code>proof</code> is valid, given the correct verifying key
and public signals.</p>
<p>Returns <code>false</code> otherwise.</p>
<p><strong><code>signMsg(privKey: EddsaPrivateKey, msg: SnarkBigInt): EdDSAMiMcSpongeSignature)</code></strong></p>
<p>Encapsualtes <code>circomlib.eddsa.signMiMCSponge</code> to sign a message <code>msg</code> using private key <code>privKey</code>.</p>
<p><strong><code>verifySignature(msg: SnarkBigInt, signature: EdDSAMiMcSpongeSignature, pubKey: EddsaPublicKey)</code>: boolean</strong></p>
<p>Returns <code>true</code> if the cryptographic <code>signature</code> of the signed <code>msg</code> is from the
private key associated with <code>pubKey</code>.</p>
<p>Returns <code>false</code> otherwise.</p>
<p><strong><code>setupTree(levels: number, prefix: string): MerkleTree</code></strong></p>
<p>Returns a Merkle tree created using
<a href="https://www.npmjs.com/package/semaphore-merkle-tree"><code>semaphore-merkle-tree</code></a>
with the same number of levels which the Semaphore zk-SNARK circuit expects.
This tree is also configured to use <code>MimcSpongeHasher</code>, which is also what the
circuit expects.</p>
<p><code>levels</code> sets the number of levels of the tree. A tree with 20 levels, for
instance, supports up to 1048576 deposits.</p>
<p><strong><code>genCircuit(circuitDefinition: any)</code></strong></p>
<p>Returns a <code>new snarkjs.Circuit(circuitDefinition)</code>. The <code>circuitDefinition</code>
object should be the <code>JSON.parse</code>d result of the <code>circom</code> command which
converts a <code>.circom</code> file to a <code>.json</code> file.</p>
<p><strong><code>async genWitness(...)</code></strong></p>
<p>This function has the following signature:</p>
<pre><code class="language-ts">const genWitness = async (
signal: string,
circuit: SnarkCircuit,
identity: Identity,
idCommitments: SnarkBigInt[] | BigInt[] | ethers.utils.BigNumber[],
treeDepth: number,
externalNullifier: SnarkBigInt,
)
</code></pre>
<ul>
<li><code>signal</code> is the string you wish to broadcast.</li>
<li><code>circuit</code> is the output of <code>genCircuit()</code>.</li>
<li><code>identity</code> is the <code>Identity</code> whose identity commitment you want to prove is
in the set of registered identities.</li>
<li><code>idCommitments</code> is an array of registered identity commmitments; i.e. the
leaves of the tree.</li>
<li><code>treeDepth</code> is the number of levels which the Merkle tree used has</li>
<li><code>externalNullifier</code> is the current external nullifier</li>
</ul>
<p>It returns an object as such:</p>
<ul>
<li><code>witness</code>: The witness to pass to <code>genProof()</code>.</li>
<li><code>signal</code>: The computed signal for Semaphore. This is the hash of the
recipient's address, relayer's address, and fee.</li>
<li><code>signalHash</code>: The hash of the computed signal.</li>
<li><code>msg</code>: The hash of the external nullifier and the signal hash</li>
<li><code>signature</code>: The signature on the above msg.</li>
<li><code>tree</code>: The Merkle tree object after it has been updated with the identity commitment</li>
<li><code>identityPath</code>: The Merkle path to the identity commmitment</li>
<li><code>identityPathIndex</code>: The leaf index of the identity commitment</li>
<li><code>identityPathElements</code>: The elements along the above Merkle path</li>
</ul>
<p>Only <code>witness</code> is essential to generate the proof; the other data is only
useful for debugging and additional off-chain checks, such as verifying the
signature and the Merkle tree root.</p>
<p><strong><code>formatForVerifierContract = (proof: SnarkProof, publicSignals: SnarkPublicSignals</code></strong></p>
<p>Converts the data in <code>proof</code> and <code>publicSignals</code> to strings and rearranges
elements of <code>proof.pi_b</code> so that <code>snarkjs</code>'s <code>verifier.sol</code> will accept it.
To be specific, it returns an object as such:</p>
<pre><code class="language-ts">{
a: [ proof.pi_a[0].toString(), proof.pi_a[1].toString() ],
b: [
[ proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString() ],
[ proof.pi_b[1][1].toString(), proof.pi_b[1][0].toString() ],
],
c: [ proof.pi_c[0].toString(), proof.pi_c[1].toString() ],
input: publicSignals.map((x) =&gt; x.toString()),
}
</code></pre>
<p><strong><code>stringifyBigInts = (obj: any) =&gt; object</code></strong></p>
<p>Encapsulates <code>snarkjs.stringifyBigInts()</code>. Makes it easy to convert <code>SnarkProof</code>s to JSON. </p>
<p><strong><code>unstringifyBigInts = (obj: any) =&gt; object</code></strong></p>
<p>Encapsulates <code>snarkjs.unstringifyBigInts()</code>. Makes it easy to convert JSON to <code>SnarkProof</code>s.</p>
<p><strong><code>genExternalNullifier = (plaintext: string) =&gt; string</code></strong></p>
<p>Each external nullifier must be at most 29 bytes large. This function
keccak-256-hashes a given <code>plaintext</code>, takes the last 29 bytes, and pads it
(from the start) with 0s, and returns the resulting hex string.</p>
<h1><a class="header" href="#multi-party-trusted-setup" id="multi-party-trusted-setup">Multi-party trusted setup</a></h1>
<p>The Semaphore authors will use the <a href="https://github.com/weijiekoh/perpetualpowersoftau/">Perpetual Powers of
Tau</a> ceremony and a random
beacon as phase 1 of the trusted setup.</p>
<p>More details about phase 2 will be released soon.</p>
<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>
<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 -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</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 -->
<script type="text/javascript">
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</body>
</html>

260
docs/quickstart.html Normal file
View File

@@ -0,0 +1,260 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Quick start - </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" class="active"><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="#quick-start" id="quick-start">Quick start</a></h1>
<p>Semaphore has been tested with Node 11.14.0 and Node 12 LTE. Use
<a href="https://github.com/nvm-sh/nvm"><code>nvm</code></a> to manage your Node version.</p>
<p>Clone this repository, install dependencies, and build the source code:</p>
<pre><code class="language-bash">git clone git@github.com:kobigurk/semaphore.git &amp;&amp; \
cd semaphore &amp;&amp; \
npm i &amp;&amp; \
npm run bootstrap &amp;&amp; \
npm run build
</code></pre>
<p>Next, either download the compiled zk-SNARK circuit, proving key, and
verification key (note that these keys are for testing purposes, and not for
production, as there is no certainty that the toxic waste was securely
discarded).</p>
<p>To download the circuit, proving key, and verification key, run:</p>
<pre><code class="language-bash"># Start from the base directory
cd circuits &amp;&amp; \
./circuits/scripts/download_snarks.sh
</code></pre>
<p>To generate the above files locally instead, run:</p>
<pre><code class="language-bash"># Start from the base directory
cd circuits &amp;&amp; \
./circuits/scripts/build_snarks.sh
</code></pre>
<p>This process should take about 45 minutes.</p>
<p>Build the Solidity contracts (you need <code>solc</code> v 0.5.12 installed in your
<code>$PATH</code>):</p>
<pre><code class="language-bash"># Start from the base directory
cd contracts &amp;&amp; \
npm run compileSol
</code></pre>
<p>Run tests while still in the <code>contracts/</code> directory:</p>
<pre><code class="language-bash"># The first command tests the Merkle tree contract and the second
# tests the Semaphore contract
npm run test-semaphore &amp;&amp; \
npm run test-mt
</code></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="howitworks.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="usage.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 previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a href="usage.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>

477
docs/searcher.js Normal file
View File

@@ -0,0 +1,477 @@
"use strict";
window.search = window.search || {};
(function search(search) {
// Search functionality
//
// You can use !hasFocus() to prevent keyhandling in your key
// event handlers while the user is typing their search.
if (!Mark || !elasticlunr) {
return;
}
//IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(search, pos) {
return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
};
}
var search_wrap = document.getElementById('search-wrapper'),
searchbar = document.getElementById('searchbar'),
searchbar_outer = document.getElementById('searchbar-outer'),
searchresults = document.getElementById('searchresults'),
searchresults_outer = document.getElementById('searchresults-outer'),
searchresults_header = document.getElementById('searchresults-header'),
searchicon = document.getElementById('search-toggle'),
content = document.getElementById('content'),
searchindex = null,
doc_urls = [],
results_options = {
teaser_word_count: 30,
limit_results: 30,
},
search_options = {
bool: "AND",
expand: true,
fields: {
title: {boost: 1},
body: {boost: 1},
breadcrumbs: {boost: 0}
}
},
mark_exclude = [],
marker = new Mark(content),
current_searchterm = "",
URL_SEARCH_PARAM = 'search',
URL_MARK_PARAM = 'highlight',
teaser_count = 0,
SEARCH_HOTKEY_KEYCODE = 83,
ESCAPE_KEYCODE = 27,
DOWN_KEYCODE = 40,
UP_KEYCODE = 38,
SELECT_KEYCODE = 13;
function hasFocus() {
return searchbar === document.activeElement;
}
function removeChildren(elem) {
while (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
}
// Helper to parse a url into its building blocks.
function parseURL(url) {
var a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
params: (function(){
var ret = {};
var seg = a.search.replace(/^\?/,'').split('&');
var len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^/])/,'/$1')
};
}
// Helper to recreate a url string from its building blocks.
function renderURL(urlobject) {
var url = urlobject.protocol + "://" + urlobject.host;
if (urlobject.port != "") {
url += ":" + urlobject.port;
}
url += urlobject.path;
var joiner = "?";
for(var prop in urlobject.params) {
if(urlobject.params.hasOwnProperty(prop)) {
url += joiner + prop + "=" + urlobject.params[prop];
joiner = "&";
}
}
if (urlobject.hash != "") {
url += "#" + urlobject.hash;
}
return url;
}
// Helper to escape html special chars for displaying the teasers
var escapeHTML = (function() {
var MAP = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
};
var repl = function(c) { return MAP[c]; };
return function(s) {
return s.replace(/[&<>'"]/g, repl);
};
})();
function formatSearchMetric(count, searchterm) {
if (count == 1) {
return count + " search result for '" + searchterm + "':";
} else if (count == 0) {
return "No search results for '" + searchterm + "'.";
} else {
return count + " search results for '" + searchterm + "':";
}
}
function formatSearchResult(result, searchterms) {
var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms);
teaser_count++;
// The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor
var url = doc_urls[result.ref].split("#");
if (url.length == 1) { // no anchor found
url.push("");
}
return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + searchterms + '#' + url[1]
+ '" aria-details="teaser_' + teaser_count + '">' + result.doc.breadcrumbs + '</a>'
+ '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">'
+ teaser + '</span>';
}
function makeTeaser(body, searchterms) {
// The strategy is as follows:
// First, assign a value to each word in the document:
// Words that correspond to search terms (stemmer aware): 40
// Normal words: 2
// First word in a sentence: 8
// Then use a sliding window with a constant number of words and count the
// sum of the values of the words within the window. Then use the window that got the
// maximum sum. If there are multiple maximas, then get the last one.
// Enclose the terms in <em>.
var stemmed_searchterms = searchterms.map(function(w) {
return elasticlunr.stemmer(w.toLowerCase());
});
var searchterm_weight = 40;
var weighted = []; // contains elements of ["word", weight, index_in_document]
// split in sentences, then words
var sentences = body.toLowerCase().split('. ');
var index = 0;
var value = 0;
var searchterm_found = false;
for (var sentenceindex in sentences) {
var words = sentences[sentenceindex].split(' ');
value = 8;
for (var wordindex in words) {
var word = words[wordindex];
if (word.length > 0) {
for (var searchtermindex in stemmed_searchterms) {
if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) {
value = searchterm_weight;
searchterm_found = true;
}
};
weighted.push([word, value, index]);
value = 2;
}
index += word.length;
index += 1; // ' ' or '.' if last word in sentence
};
index += 1; // because we split at a two-char boundary '. '
};
if (weighted.length == 0) {
return body;
}
var window_weight = [];
var window_size = Math.min(weighted.length, results_options.teaser_word_count);
var cur_sum = 0;
for (var wordindex = 0; wordindex < window_size; wordindex++) {
cur_sum += weighted[wordindex][1];
};
window_weight.push(cur_sum);
for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) {
cur_sum -= weighted[wordindex][1];
cur_sum += weighted[wordindex + window_size][1];
window_weight.push(cur_sum);
};
if (searchterm_found) {
var max_sum = 0;
var max_sum_window_index = 0;
// backwards
for (var i = window_weight.length - 1; i >= 0; i--) {
if (window_weight[i] > max_sum) {
max_sum = window_weight[i];
max_sum_window_index = i;
}
};
} else {
max_sum_window_index = 0;
}
// add <em/> around searchterms
var teaser_split = [];
var index = weighted[max_sum_window_index][2];
for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) {
var word = weighted[i];
if (index < word[2]) {
// missing text from index to start of `word`
teaser_split.push(body.substring(index, word[2]));
index = word[2];
}
if (word[1] == searchterm_weight) {
teaser_split.push("<em>")
}
index = word[2] + word[0].length;
teaser_split.push(body.substring(word[2], index));
if (word[1] == searchterm_weight) {
teaser_split.push("</em>")
}
};
return teaser_split.join('');
}
function init(config) {
results_options = config.results_options;
search_options = config.search_options;
searchbar_outer = config.searchbar_outer;
doc_urls = config.doc_urls;
searchindex = elasticlunr.Index.load(config.index);
// Set up events
searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false);
searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false);
document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false);
// If the user uses the browser buttons, do the same as if a reload happened
window.onpopstate = function(e) { doSearchOrMarkFromUrl(); };
// Suppress "submit" events so the page doesn't reload when the user presses Enter
document.addEventListener('submit', function(e) { e.preventDefault(); }, false);
// If reloaded, do the search or mark again, depending on the current url parameters
doSearchOrMarkFromUrl();
}
function unfocusSearchbar() {
// hacky, but just focusing a div only works once
var tmp = document.createElement('input');
tmp.setAttribute('style', 'position: absolute; opacity: 0;');
searchicon.appendChild(tmp);
tmp.focus();
tmp.remove();
}
// On reload or browser history backwards/forwards events, parse the url and do search or mark
function doSearchOrMarkFromUrl() {
// Check current URL for search request
var url = parseURL(window.location.href);
if (url.params.hasOwnProperty(URL_SEARCH_PARAM)
&& url.params[URL_SEARCH_PARAM] != "") {
showSearch(true);
searchbar.value = decodeURIComponent(
(url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20'));
searchbarKeyUpHandler(); // -> doSearch()
} else {
showSearch(false);
}
if (url.params.hasOwnProperty(URL_MARK_PARAM)) {
var words = url.params[URL_MARK_PARAM].split(' ');
marker.mark(words, {
exclude: mark_exclude
});
var markers = document.querySelectorAll("mark");
function hide() {
for (var i = 0; i < markers.length; i++) {
markers[i].classList.add("fade-out");
window.setTimeout(function(e) { marker.unmark(); }, 300);
}
}
for (var i = 0; i < markers.length; i++) {
markers[i].addEventListener('click', hide);
}
}
}
// Eventhandler for keyevents on `document`
function globalKeyHandler(e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea') { return; }
if (e.keyCode === ESCAPE_KEYCODE) {
e.preventDefault();
searchbar.classList.remove("active");
setSearchUrlParameters("",
(searchbar.value.trim() !== "") ? "push" : "replace");
if (hasFocus()) {
unfocusSearchbar();
}
showSearch(false);
marker.unmark();
} else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) {
e.preventDefault();
showSearch(true);
window.scrollTo(0, 0);
searchbar.select();
} else if (hasFocus() && e.keyCode === DOWN_KEYCODE) {
e.preventDefault();
unfocusSearchbar();
searchresults.firstElementChild.classList.add("focus");
} else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE
|| e.keyCode === UP_KEYCODE
|| e.keyCode === SELECT_KEYCODE)) {
// not `:focus` because browser does annoying scrolling
var focused = searchresults.querySelector("li.focus");
if (!focused) return;
e.preventDefault();
if (e.keyCode === DOWN_KEYCODE) {
var next = focused.nextElementSibling;
if (next) {
focused.classList.remove("focus");
next.classList.add("focus");
}
} else if (e.keyCode === UP_KEYCODE) {
focused.classList.remove("focus");
var prev = focused.previousElementSibling;
if (prev) {
prev.classList.add("focus");
} else {
searchbar.select();
}
} else { // SELECT_KEYCODE
window.location.assign(focused.querySelector('a'));
}
}
}
function showSearch(yes) {
if (yes) {
search_wrap.classList.remove('hidden');
searchicon.setAttribute('aria-expanded', 'true');
} else {
search_wrap.classList.add('hidden');
searchicon.setAttribute('aria-expanded', 'false');
var results = searchresults.children;
for (var i = 0; i < results.length; i++) {
results[i].classList.remove("focus");
}
}
}
function showResults(yes) {
if (yes) {
searchresults_outer.classList.remove('hidden');
} else {
searchresults_outer.classList.add('hidden');
}
}
// Eventhandler for search icon
function searchIconClickHandler() {
if (search_wrap.classList.contains('hidden')) {
showSearch(true);
window.scrollTo(0, 0);
searchbar.select();
} else {
showSearch(false);
}
}
// Eventhandler for keyevents while the searchbar is focused
function searchbarKeyUpHandler() {
var searchterm = searchbar.value.trim();
if (searchterm != "") {
searchbar.classList.add("active");
doSearch(searchterm);
} else {
searchbar.classList.remove("active");
showResults(false);
removeChildren(searchresults);
}
setSearchUrlParameters(searchterm, "push_if_new_search_else_replace");
// Remove marks
marker.unmark();
}
// Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor .
// `action` can be one of "push", "replace", "push_if_new_search_else_replace"
// and replaces or pushes a new browser history item.
// "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet.
function setSearchUrlParameters(searchterm, action) {
var url = parseURL(window.location.href);
var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM);
if (searchterm != "" || action == "push_if_new_search_else_replace") {
url.params[URL_SEARCH_PARAM] = searchterm;
delete url.params[URL_MARK_PARAM];
url.hash = "";
} else {
delete url.params[URL_SEARCH_PARAM];
}
// A new search will also add a new history item, so the user can go back
// to the page prior to searching. A updated search term will only replace
// the url.
if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) {
history.pushState({}, document.title, renderURL(url));
} else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) {
history.replaceState({}, document.title, renderURL(url));
}
}
function doSearch(searchterm) {
// Don't search the same twice
if (current_searchterm == searchterm) { return; }
else { current_searchterm = searchterm; }
if (searchindex == null) { return; }
// Do the actual search
var results = searchindex.search(searchterm, search_options);
var resultcount = Math.min(results.length, results_options.limit_results);
// Display search metrics
searchresults_header.innerText = formatSearchMetric(resultcount, searchterm);
// Clear and insert results
var searchterms = searchterm.split(' ');
removeChildren(searchresults);
for(var i = 0; i < resultcount ; i++){
var resultElem = document.createElement('li');
resultElem.innerHTML = formatSearchResult(results[i], searchterms);
searchresults.appendChild(resultElem);
}
// Display results
showResults(true);
}
fetch(path_to_root + 'searchindex.json')
.then(response => response.json())
.then(json => init(json))
.catch(error => { // Try to load searchindex.js if fetch failed
var script = document.createElement('script');
script.src = path_to_root + 'searchindex.js';
script.onload = () => init(window.search);
document.head.appendChild(script);
});
// Exported functions
search.hasFocus = hasFocus;
})(window.search);

1
docs/searchindex.js Normal file

File diff suppressed because one or more lines are too long

1
docs/searchindex.json Normal file

File diff suppressed because one or more lines are too long

104
docs/tomorrow-night.css Normal file
View File

@@ -0,0 +1,104 @@
/* Tomorrow Night Theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
/* Tomorrow Comment */
.hljs-comment {
color: #969896;
}
/* Tomorrow Red */
.hljs-variable,
.hljs-attribute,
.hljs-tag,
.hljs-regexp,
.ruby .hljs-constant,
.xml .hljs-tag .hljs-title,
.xml .hljs-pi,
.xml .hljs-doctype,
.html .hljs-doctype,
.css .hljs-id,
.css .hljs-class,
.css .hljs-pseudo {
color: #cc6666;
}
/* Tomorrow Orange */
.hljs-number,
.hljs-preprocessor,
.hljs-pragma,
.hljs-built_in,
.hljs-literal,
.hljs-params,
.hljs-constant {
color: #de935f;
}
/* Tomorrow Yellow */
.ruby .hljs-class .hljs-title,
.css .hljs-rule .hljs-attribute {
color: #f0c674;
}
/* Tomorrow Green */
.hljs-string,
.hljs-value,
.hljs-inheritance,
.hljs-header,
.hljs-name,
.ruby .hljs-symbol,
.xml .hljs-cdata {
color: #b5bd68;
}
/* Tomorrow Aqua */
.hljs-title,
.css .hljs-hexcolor {
color: #8abeb7;
}
/* Tomorrow Blue */
.hljs-function,
.python .hljs-decorator,
.python .hljs-title,
.ruby .hljs-function .hljs-title,
.ruby .hljs-title .hljs-keyword,
.perl .hljs-sub,
.javascript .hljs-title,
.coffeescript .hljs-title {
color: #81a2be;
}
/* Tomorrow Purple */
.hljs-keyword,
.javascript .hljs-function {
color: #b294bb;
}
.hljs {
display: block;
overflow-x: auto;
background: #1d1f21;
color: #c5c8c6;
padding: 0.5em;
-webkit-text-size-adjust: none;
}
.coffeescript .javascript,
.javascript .xml,
.tex .hljs-formula,
.xml .javascript,
.xml .vbscript,
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
}
.hljs-addition {
color: #718c00;
}
.hljs-deletion {
color: #c82829;
}

224
docs/trustedsetup.html Normal file
View File

@@ -0,0 +1,224 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Trusted setup - </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" class="active"><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="#multi-party-trusted-setup" id="multi-party-trusted-setup">Multi-party trusted setup</a></h1>
<p>The Semaphore authors will use the <a href="https://github.com/weijiekoh/perpetualpowersoftau/">Perpetual Powers of
Tau</a> ceremony and a random
beacon as phase 1 of the trusted setup.</p>
<p>More details about phase 2 will be released soon.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="libsemaphore.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="audit.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="libsemaphore.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="audit.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>

315
docs/usage.html Normal file
View File

@@ -0,0 +1,315 @@
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Usage - </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" class="active"><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="#usage" id="usage">Usage</a></h1>
<p>The Semaphore contract forms a base layer for other contracts to create
applications that rely on anonymous signaling.</p>
<p>First, you should ensure that the proving key, verification key, and circuit
file, which are static, be easily available to your users. These may be hosted
in a CDN or bundled with your application code.</p>
<p>The Semaphore team has not performed a trusted setup yet, so trustworthy
versions of these files are not available yet.</p>
<p>Untrusted versions of these files, however, may be obtained via the
<code>circuits/scripts/download_snarks.sh</code> script.</p>
<p>Next, to have full flexibility over Semaphore's mechanisms, write a Client
contract and set the owner of the Semaphore contract as the address of the
Client contract. You may also write a Client contract which deploys a Semaphore
contract in its constructor, or on the fly. </p>
<p>With the Client contract as the owner of the Semaphore contract, the Client
contract may call owner-only Semaphore functions such as
<code>addExternalNullifier()</code>.</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>These functions add, deactivate, and reactivate an external nullifier respectively.
As each identity can only signal once to an external nullifier, and as a signal
can only be successfully broadcasted to an active external nullifier, these
functions enable use cases where it is necessary to have multiple external
nullifiers or to activate and/or deactivate them.</p>
<p>Refer to the <a href="https://medium.com/coinmonks/to-mixers-and-beyond-presenting-semaphore-a-privacy-gadget-built-on-ethereum-4c8b00857c9b">high-level explanation of
Semaphore</a>
for more details.</p>
<h2><a class="header" href="#set-broadcast-permissioning" id="set-broadcast-permissioning">Set broadcast permissioning</a></h2>
<p>Note that <code>Semaphore.broadcastSignal()</code> is permissioned by default, so if you
wish for anyone to be able to broadcast a signal, the owner of the Semaphore
contract (either a Client contract or externally owned account) must first
invoke <code>setPermissioning(false)</code>.</p>
<p>See <a href="https://github.com/appliedzkp/semaphore/blob/master/contracts/sol/SemaphoreClient.sol">SemaphoreClient.sol</a> for an example.</p>
<h2><a class="header" href="#insert-identities" id="insert-identities">Insert identities</a></h2>
<p>To generate an identity commitment, use the <code>libsemaphore</code> functions
<code>genIdentity()</code> and <code>genIdentityCommitment()</code> Typescript (or Javascript)
functions:</p>
<pre><code class="language-ts">const identity: Identity = genIdentity()
const identityCommitment = genIdentityCommitment(identity)
</code></pre>
<p>Be sure to store <code>identity</code> somewhere safe. The <code>serialiseIdentity()</code> function
can help with this:</p>
<p><code>const serialisedId: string = serialiseIdentity(identity: Identity)</code></p>
<p>It converts an <code>Identity</code> into a JSON string which looks like this:</p>
<pre><code class="language-text">[&quot;e82cc2b8654705e427df423c6300307a873a2e637028fab3163cf95b18bb172e&quot;,&quot;a02e517dfb3a4184adaa951d02bfe0fe092d1ee34438721d798db75b8db083&quot;,&quot;15c6540bf7bddb0616984fccda7e954a0fb5ea4679ac686509dc4bd7ba9c3b&quot;]
</code></pre>
<p>To convert this string back into an <code>Identity</code>, use <code>unSerialiseIdentity()</code>.</p>
<p><code>const id: Identity = unSerialiseIdentity(serialisedId)</code></p>
<h2><a class="header" href="#broadcast-signals" id="broadcast-signals">Broadcast signals</a></h2>
<p>First obtain the leaves of the identity tree (in sequence, up to the user's
identity commitment, or more).</p>
<pre><code class="language-ts">const leaves = &lt;list of leaves&gt;
</code></pre>
<p>Next, load the circuit from disk (or from a remote source):</p>
<pre><code class="language-ts">const circuitPath = path.join(__dirname, '/path/to/circuit.json')
const cirDef = JSON.parse(fs.readFileSync(circuitPath).toString())
const circuit = genCircuit(cirDef)
</code></pre>
<p>Next, use <code>libsemaphore</code>'s <code>genWitness()</code> helper function as such:</p>
<pre><code>const result = await genWitness(
signal,
circuit,
identity,
leaves,
num_levels,
external_nullifier,
)
</code></pre>
<ul>
<li><code>signal</code>: a string which is the signal to broadcast.</li>
<li><code>circuit</code>: the output of <code>genCircuit()</code> (see above).</li>
<li><code>identity</code>: the user's identity as an <code>Identity</code> object.</li>
<li><code>leaves</code> the list of leaves in the tree (see above).</li>
<li><code>num_levels</code>: the depth of the Merkle tree.</li>
<li><code>external_nullifier</code>: the external nullifier at which to broadcast.</li>
</ul>
<p>Load the proving key from disk (or from a remote source):</p>
<pre><code class="language-ts">const provingKeyPath = path.join(__dirname, '/path/to/proving_key.bin')
const provingKey: SnarkProvingKey = fs.readFileSync(provingKeyPath)
</code></pre>
<p>Generate the proof (this takes about 30-45 seconds on a modern laptop):</p>
<pre><code class="language-ts">const proof = await genProof(result.witness, provingKey)
</code></pre>
<p>Generate the <code>broadcastSignal()</code> parameters:</p>
<pre><code class="language-ts">const publicSignals = genPublicSignals(result.witness, circuit)
const params = genBroadcastSignalParams(result, proof, publicSignals)
</code></pre>
<p>Finally, invoke <code>broadcastSignal()</code> with the parameters:</p>
<pre><code class="language-ts">const tx = await semaphoreClientContract.broadcastSignal(
ethers.utils.toUtf8Bytes(signal),
params.proof,
params.root,
params.nullifiersHash,
external_nullifier,
{ gasLimit: 500000 },
)
</code></pre>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="quickstart.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="api.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="quickstart.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="api.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>

1
docs_src/book/.nojekyll Normal file
View File

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

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

307
docs_src/book/about.html Normal file
View File

@@ -0,0 +1,307 @@
<!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>Semaphore 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>
<!-- Livereload script (if served using the cli tool) -->
<script type="text/javascript">
var socket = new WebSocket("ws://localhost:3001");
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload(true); // force reload from server (not from cache)
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<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>

314
docs_src/book/api.html Normal file
View File

@@ -0,0 +1,314 @@
<!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>
<!-- Livereload script (if served using the cli tool) -->
<script type="text/javascript">
var socket = new WebSocket("ws://localhost:3001");
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload(true); // force reload from server (not from cache)
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<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>

243
docs_src/book/audit.html Normal file
View File

@@ -0,0 +1,243 @@
<!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>
<!-- Livereload script (if served using the cli tool) -->
<script type="text/javascript">
var socket = new WebSocket("ws://localhost:3001");
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload(true); // force reload from server (not from cache)
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<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>

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_src/book/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_src/book/clipboard.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