mirror of
https://github.com/semaphore-protocol/semaphore.git
synced 2026-01-11 15:48:12 -05:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
236e6b241c | ||
|
|
e259482ad8 | ||
|
|
76101a0c54 | ||
|
|
8489d149c7 | ||
|
|
dc0298599b | ||
|
|
55fa4203d2 | ||
|
|
1cd0e5f9d7 | ||
|
|
08961c0197 | ||
|
|
bbe13aa12a | ||
|
|
d5a9ba4b60 | ||
|
|
ba5f2a038f | ||
|
|
375b79628e | ||
|
|
242924fff4 | ||
|
|
18e0afc711 | ||
|
|
ab9a0e84f3 | ||
|
|
be42bcf8eb | ||
|
|
b3fc357ecf | ||
|
|
5ba953ecd0 | ||
|
|
13121a907c | ||
|
|
648bf9a097 | ||
|
|
24340b0692 |
@@ -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
8
.gitignore
vendored
Normal 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
172
README.md
@@ -1,168 +1,12 @@
|
||||
# Semaphore
|
||||
|
||||
[](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:
|
||||
|
||||

|
||||
|
||||
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"`
|
||||
|
||||
@@ -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++) {
|
||||
30
circuits/circom/blake2s/uint32.circom
Normal file
30
circuits/circom/blake2s/uint32.circom
Normal file
@@ -0,0 +1,30 @@
|
||||
include "../../node_modules/circomlib/circuits/binsum.circom";
|
||||
|
||||
template Uint32Add(n) {
|
||||
signal input nums_bits[n][32];
|
||||
signal output out_bits[32];
|
||||
|
||||
component sum = BinSum(32, n);
|
||||
var i;
|
||||
var j;
|
||||
for (i = 0; i < n; i++) {
|
||||
for (j = 0; j < 32; j++) {
|
||||
sum.in[i][j] <== nums_bits[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < 32; j++) {
|
||||
out_bits[j] <== sum.out[j];
|
||||
}
|
||||
}
|
||||
|
||||
template Uint32Xor() {
|
||||
signal input a_bits[32];
|
||||
signal input b_bits[32];
|
||||
|
||||
signal output out_bits[32];
|
||||
|
||||
for (var i = 0; i < 32; i++) {
|
||||
out_bits[i] <== a_bits[i] + b_bits[i] - 2*a_bits[i]*b_bits[i];
|
||||
}
|
||||
}
|
||||
258
circuits/circom/semaphore-base.circom
Normal file
258
circuits/circom/semaphore-base.circom
Normal file
@@ -0,0 +1,258 @@
|
||||
include "../node_modules/circomlib/circuits/pedersen.circom";
|
||||
include "../node_modules/circomlib/circuits/mimcsponge.circom";
|
||||
include "../node_modules/circomlib/circuits/bitify.circom";
|
||||
include "../node_modules/circomlib/circuits/eddsamimcsponge.circom";
|
||||
include "../node_modules/circomlib/circuits/babyjub.circom";
|
||||
include "../node_modules/circomlib/circuits/mux1.circom";
|
||||
include "../node_modules/circomlib/circuits/assert.circom";
|
||||
include "./blake2s/blake2s.circom";
|
||||
|
||||
template HashLeftRight() {
|
||||
signal input left;
|
||||
signal input right;
|
||||
|
||||
signal output hash;
|
||||
|
||||
component hasher = MiMCSponge(2, 1);
|
||||
left ==> hasher.ins[0];
|
||||
right ==> hasher.ins[1];
|
||||
hasher.k <== 0;
|
||||
|
||||
hash <== hasher.outs[0];
|
||||
}
|
||||
|
||||
template Selector() {
|
||||
signal input input_elem;
|
||||
signal input path_elem;
|
||||
signal input path_index;
|
||||
|
||||
signal output left;
|
||||
signal output right;
|
||||
|
||||
path_index * (1-path_index) === 0
|
||||
|
||||
component mux = MultiMux1(2);
|
||||
mux.c[0][0] <== input_elem;
|
||||
mux.c[0][1] <== path_elem;
|
||||
|
||||
mux.c[1][0] <== path_elem;
|
||||
mux.c[1][1] <== input_elem;
|
||||
|
||||
mux.s <== path_index;
|
||||
|
||||
left <== mux.out[0];
|
||||
right <== mux.out[1];
|
||||
}
|
||||
|
||||
template MerkleTreeInclusionProof(n_levels) {
|
||||
signal input identity_commitment;
|
||||
signal input identity_path_index[n_levels];
|
||||
signal input identity_path_elements[n_levels];
|
||||
signal output root;
|
||||
|
||||
component selectors[n_levels];
|
||||
component hashers[n_levels];
|
||||
|
||||
for (var i = 0; i < n_levels; i++) {
|
||||
selectors[i] = Selector();
|
||||
hashers[i] = HashLeftRight();
|
||||
|
||||
identity_path_index[i] ==> selectors[i].path_index;
|
||||
identity_path_elements[i] ==> selectors[i].path_elem;
|
||||
|
||||
selectors[i].left ==> hashers[i].left;
|
||||
selectors[i].right ==> hashers[i].right;
|
||||
}
|
||||
|
||||
identity_commitment ==> selectors[0].input_elem;
|
||||
|
||||
for (var i = 1; i < n_levels; i++) {
|
||||
hashers[i-1].hash ==> selectors[i].input_elem;
|
||||
}
|
||||
|
||||
root <== hashers[n_levels - 1].hash;
|
||||
}
|
||||
|
||||
template CalculateIdentityCommitment(IDENTITY_PK_SIZE_IN_BITS, NULLIFIER_TRAPDOOR_SIZE_IN_BITS) {
|
||||
signal input identity_pk[IDENTITY_PK_SIZE_IN_BITS];
|
||||
signal input identity_nullifier[NULLIFIER_TRAPDOOR_SIZE_IN_BITS];
|
||||
signal input identity_trapdoor[NULLIFIER_TRAPDOOR_SIZE_IN_BITS];
|
||||
|
||||
signal output out;
|
||||
|
||||
// identity commitment is a pedersen hash of (identity_pk, identity_nullifier, identity_trapdoor), each element padded up to 256 bits
|
||||
component identity_commitment = Pedersen(3*256);
|
||||
for (var i = 0; i < 256; i++) {
|
||||
if (i < IDENTITY_PK_SIZE_IN_BITS) {
|
||||
identity_commitment.in[i] <== identity_pk[i];
|
||||
} else {
|
||||
identity_commitment.in[i] <== 0;
|
||||
}
|
||||
|
||||
if (i < NULLIFIER_TRAPDOOR_SIZE_IN_BITS) {
|
||||
identity_commitment.in[i + 256] <== identity_nullifier[i];
|
||||
identity_commitment.in[i + 2*256] <== identity_trapdoor[i];
|
||||
} else {
|
||||
identity_commitment.in[i + 256] <== 0;
|
||||
identity_commitment.in[i + 2*256] <== 0;
|
||||
}
|
||||
}
|
||||
|
||||
out <== identity_commitment.out[0];
|
||||
}
|
||||
|
||||
template CalculateNullifier(NULLIFIER_TRAPDOOR_SIZE_IN_BITS, EXTERNAL_NULLIFIER_SIZE_IN_BITS, n_levels) {
|
||||
signal input external_nullifier;
|
||||
signal input identity_nullifier[NULLIFIER_TRAPDOOR_SIZE_IN_BITS];
|
||||
signal input identity_path_index[n_levels];
|
||||
|
||||
signal output nullifiers_hash;
|
||||
|
||||
component external_nullifier_bits = Num2Bits(EXTERNAL_NULLIFIER_SIZE_IN_BITS);
|
||||
external_nullifier_bits.in <== external_nullifier;
|
||||
|
||||
var nullifiers_hasher_bits = NULLIFIER_TRAPDOOR_SIZE_IN_BITS + EXTERNAL_NULLIFIER_SIZE_IN_BITS + n_levels;
|
||||
if (nullifiers_hasher_bits < 512) {
|
||||
nullifiers_hasher_bits = 512;
|
||||
}
|
||||
assert (nullifiers_hasher_bits <= 512);
|
||||
|
||||
component nullifiers_hasher = Blake2s(nullifiers_hasher_bits, 0);
|
||||
for (var i = 0; i < NULLIFIER_TRAPDOOR_SIZE_IN_BITS; i++) {
|
||||
nullifiers_hasher.in_bits[i] <== identity_nullifier[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < EXTERNAL_NULLIFIER_SIZE_IN_BITS; i++) {
|
||||
nullifiers_hasher.in_bits[NULLIFIER_TRAPDOOR_SIZE_IN_BITS + i] <== external_nullifier_bits.out[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < n_levels; i++) {
|
||||
nullifiers_hasher.in_bits[NULLIFIER_TRAPDOOR_SIZE_IN_BITS + EXTERNAL_NULLIFIER_SIZE_IN_BITS + i] <== identity_path_index[i];
|
||||
}
|
||||
|
||||
for (var i = (NULLIFIER_TRAPDOOR_SIZE_IN_BITS + EXTERNAL_NULLIFIER_SIZE_IN_BITS + n_levels); i < nullifiers_hasher_bits; i++) {
|
||||
nullifiers_hasher.in_bits[i] <== 0;
|
||||
}
|
||||
|
||||
component nullifiers_hash_num = Bits2Num(250);
|
||||
for (var i = 0; i < 250; i++) {
|
||||
nullifiers_hash_num.in[i] <== nullifiers_hasher.out[i];
|
||||
}
|
||||
|
||||
nullifiers_hash <== nullifiers_hash_num.out;
|
||||
}
|
||||
|
||||
// n_levels must be < 32
|
||||
template Semaphore(n_levels) {
|
||||
// BEGIN signals
|
||||
|
||||
signal input signal_hash;
|
||||
signal input external_nullifier;
|
||||
|
||||
signal private input fake_zero;
|
||||
|
||||
// mimc vector commitment
|
||||
signal private input identity_pk[2];
|
||||
signal private input identity_nullifier;
|
||||
signal private input identity_trapdoor;
|
||||
signal private input identity_path_elements[n_levels];
|
||||
signal private input identity_path_index[n_levels];
|
||||
|
||||
// signature on (external nullifier, signal_hash) with identity_pk
|
||||
signal private input auth_sig_r[2];
|
||||
signal private input auth_sig_s;
|
||||
|
||||
// mimc hash
|
||||
signal output root;
|
||||
signal output nullifiers_hash;
|
||||
|
||||
// END signals
|
||||
|
||||
// BEGIN constants
|
||||
|
||||
var IDENTITY_PK_SIZE_IN_BITS = 254;
|
||||
var NULLIFIER_TRAPDOOR_SIZE_IN_BITS = 248;
|
||||
var EXTERNAL_NULLIFIER_SIZE_IN_BITS = 232;
|
||||
|
||||
// END constants
|
||||
|
||||
fake_zero === 0;
|
||||
|
||||
component verify_identity_pk_on_curve = BabyCheck();
|
||||
verify_identity_pk_on_curve.x <== identity_pk[0];
|
||||
verify_identity_pk_on_curve.y <== identity_pk[1];
|
||||
|
||||
component verify_auth_sig_r_on_curve = BabyCheck();
|
||||
verify_auth_sig_r_on_curve.x <== auth_sig_r[0];
|
||||
verify_auth_sig_r_on_curve.y <== auth_sig_r[1];
|
||||
|
||||
// get a prime subgroup element derived from identity_pk
|
||||
component dbl1 = BabyDbl();
|
||||
dbl1.x <== identity_pk[0];
|
||||
dbl1.y <== identity_pk[1];
|
||||
component dbl2 = BabyDbl();
|
||||
dbl2.x <== dbl1.xout;
|
||||
dbl2.y <== dbl1.yout;
|
||||
component dbl3 = BabyDbl();
|
||||
dbl3.x <== dbl2.xout;
|
||||
dbl3.y <== dbl2.yout;
|
||||
|
||||
component identity_nullifier_bits = Num2Bits(NULLIFIER_TRAPDOOR_SIZE_IN_BITS);
|
||||
identity_nullifier_bits.in <== identity_nullifier;
|
||||
|
||||
component identity_trapdoor_bits = Num2Bits(NULLIFIER_TRAPDOOR_SIZE_IN_BITS);
|
||||
identity_trapdoor_bits.in <== identity_trapdoor;
|
||||
|
||||
component identity_pk_0_bits = Num2Bits_strict();
|
||||
identity_pk_0_bits.in <== dbl3.xout;
|
||||
|
||||
// BEGIN identity commitment
|
||||
component identity_commitment = CalculateIdentityCommitment(IDENTITY_PK_SIZE_IN_BITS, NULLIFIER_TRAPDOOR_SIZE_IN_BITS);
|
||||
for (var i = 0; i < IDENTITY_PK_SIZE_IN_BITS; i++) {
|
||||
identity_commitment.identity_pk[i] <== identity_pk_0_bits.out[i];
|
||||
}
|
||||
for (var i = 0; i < NULLIFIER_TRAPDOOR_SIZE_IN_BITS; i++) {
|
||||
identity_commitment.identity_nullifier[i] <== identity_nullifier_bits.out[i];
|
||||
identity_commitment.identity_trapdoor[i] <== identity_trapdoor_bits.out[i];
|
||||
}
|
||||
// END identity commitment
|
||||
|
||||
// BEGIN tree
|
||||
component tree = MerkleTreeInclusionProof(n_levels);
|
||||
tree.identity_commitment <== identity_commitment.out;
|
||||
for (var i = 0; i < n_levels; i++) {
|
||||
tree.identity_path_index[i] <== identity_path_index[i];
|
||||
tree.identity_path_elements[i] <== identity_path_elements[i];
|
||||
}
|
||||
root <== tree.root;
|
||||
// END tree
|
||||
|
||||
// BEGIN nullifiers
|
||||
component nullifiers_hasher = CalculateNullifier(NULLIFIER_TRAPDOOR_SIZE_IN_BITS, EXTERNAL_NULLIFIER_SIZE_IN_BITS, n_levels);
|
||||
nullifiers_hasher.external_nullifier <== external_nullifier;
|
||||
for (var i = 0; i < NULLIFIER_TRAPDOOR_SIZE_IN_BITS; i++) {
|
||||
nullifiers_hasher.identity_nullifier[i] <== identity_nullifier_bits.out[i];
|
||||
}
|
||||
for (var i = 0; i < n_levels; i++) {
|
||||
nullifiers_hasher.identity_path_index[i] <== identity_path_index[i];
|
||||
}
|
||||
nullifiers_hash <== nullifiers_hasher.nullifiers_hash;
|
||||
// END nullifiers
|
||||
|
||||
// BEGIN verify sig
|
||||
component msg_hasher = MiMCSponge(2, 1);
|
||||
msg_hasher.ins[0] <== external_nullifier;
|
||||
msg_hasher.ins[1] <== signal_hash;
|
||||
msg_hasher.k <== 0;
|
||||
|
||||
component sig_verifier = EdDSAMiMCSpongeVerifier();
|
||||
(1 - fake_zero) ==> sig_verifier.enabled;
|
||||
identity_pk[0] ==> sig_verifier.Ax;
|
||||
identity_pk[1] ==> sig_verifier.Ay;
|
||||
auth_sig_r[0] ==> sig_verifier.R8x;
|
||||
auth_sig_r[1] ==> sig_verifier.R8y;
|
||||
auth_sig_s ==> sig_verifier.S;
|
||||
msg_hasher.outs[0] ==> sig_verifier.M;
|
||||
|
||||
// END verify sig
|
||||
}
|
||||
3
circuits/circom/semaphore.circom
Normal file
3
circuits/circom/semaphore.circom
Normal file
@@ -0,0 +1,3 @@
|
||||
include "./semaphore-base.circom";
|
||||
|
||||
component main = Semaphore(20);
|
||||
29
circuits/jest.config.js
Normal file
29
circuits/jest.config.js
Normal file
@@ -0,0 +1,29 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
transform: {
|
||||
"^.+\\.tsx?$": 'ts-jest'
|
||||
},
|
||||
testPathIgnorePatterns: [
|
||||
"/build/",
|
||||
"/node_modules/",
|
||||
],
|
||||
testRegex: '/__tests__/.*\\.test\\.ts$',
|
||||
moduleFileExtensions: [
|
||||
'ts',
|
||||
'tsx',
|
||||
'js',
|
||||
'jsx',
|
||||
'json',
|
||||
'node'
|
||||
],
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
diagnostics: {
|
||||
// Do not fail on TS compilation errors
|
||||
// https://kulshekhar.github.io/ts-jest/user/config/diagnostics#do-not-fail-on-first-error
|
||||
warnOnly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
testEnvironment: 'node'
|
||||
}
|
||||
8451
circuits/package-lock.json
generated
Normal file
8451
circuits/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
circuits/package.json
Normal file
24
circuits/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "semaphore-circuits",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc",
|
||||
"test-blake2s": "NODE_ENV=local-dev jest --forceExit --testPathPattern Blake2s.test.ts Uint32.test.ts"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.0.20",
|
||||
"@types/node": "^12.7.7",
|
||||
"circom": "0.0.34",
|
||||
"circomlib": "git+https://github.com/kobigurk/circomlib.git#4284dc1ef984a204db08864f5da530c97f9376ef",
|
||||
"jest": "^24.9.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"snarkjs": "https://github.com/weijiekoh/snarkjs.git#ef8bbbbe5a7d37f59cdb45d3fdf2c1dcf7dd9c7a",
|
||||
"ts-jest": "^24.1.0",
|
||||
"typescript": "^3.7.3",
|
||||
"websnark": "0.0.5"
|
||||
}
|
||||
}
|
||||
58
circuits/scripts/build_snarks.sh
Executable file
58
circuits/scripts/build_snarks.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# semaphorejs - Zero-knowledge signaling on Ethereum
|
||||
# Copyright (C) 2019 Kobi Gurkan <kobigurk@gmail.com>
|
||||
#
|
||||
# This file is part of semaphorejs.
|
||||
#
|
||||
# semaphorejs is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# semaphorejs is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with semaphorejs. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
mkdir -p ../build
|
||||
cd ../build
|
||||
|
||||
if [ -f ./circuit.json ]; then
|
||||
echo "circuit.json already exists. Skipping."
|
||||
else
|
||||
echo 'Generating circuit.json'
|
||||
export NODE_OPTIONS=--max-old-space-size=4096
|
||||
npx circom ../circom/semaphore.circom
|
||||
fi
|
||||
|
||||
if [ -f ./proving_key.json ]; then
|
||||
echo "proving_key.json already exists. Skipping."
|
||||
else
|
||||
echo 'Generating proving_key.json'
|
||||
export NODE_OPTIONS=--max-old-space-size=4096
|
||||
npx snarkjs setup --protocol groth
|
||||
fi
|
||||
|
||||
if [ -f ./proving_key.bin ]; then
|
||||
echo 'proving_key.bin already exists. Skipping.'
|
||||
else
|
||||
echo 'Generating proving_key.bin'
|
||||
export NODE_OPTIONS=--max-old-space-size=4096
|
||||
node ../node_modules/websnark/tools/buildpkey.js -i ./proving_key.json -o ./proving_key.bin
|
||||
fi
|
||||
|
||||
if [ -f ./verifier.sol ]; then
|
||||
echo 'verifier.sol already exists. Skipping.'
|
||||
else
|
||||
echo 'Generating verifier.sol'
|
||||
npx snarkjs generateverifier --vk ./verification_key.json -v ./verifier.sol
|
||||
fi
|
||||
|
||||
# Copy verifier.sol to the contracts/sol directory
|
||||
echo 'Copying verifier.sol to contracts/sol.'
|
||||
cp ./verifier.sol ../../contracts/sol/
|
||||
12
circuits/scripts/checksum_snarks.sh
Executable file
12
circuits/scripts/checksum_snarks.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Used by the CircleCI process to generate a checksum of the snark files so
|
||||
# that it can cache them.
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
mkdir -p ../build
|
||||
cd ../build
|
||||
|
||||
find ../circom -type f -exec md5sum {} \; | sort -k 2 | md5sum > ./.snark_checksum
|
||||
echo 'snark checksum:'
|
||||
cat .snark_checksum
|
||||
35
circuits/scripts/download_snarks.sh
Executable file
35
circuits/scripts/download_snarks.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
CIRCUIT_JSON="https://www.dropbox.com/s/3gzxjibqgb6ke13/circuit.json?dl=1"
|
||||
PROVING_KEY_BIN="https://www.dropbox.com/s/qjlu6v125g7jkcq/proving_key.bin?dl=1"
|
||||
VERIFICATION_KEY_JSON="https://www.dropbox.com/s/rwjwu31c7pzhsth/verification_key.json?dl=1"
|
||||
VERIFIER_SOL="https://www.dropbox.com/s/q5fjzu4zxhc0393/verifier.sol?dl=1"
|
||||
|
||||
CIRCUIT_JSON_PATH="../build/circuit.json"
|
||||
PROVING_KEY_BIN_PATH="../build/proving_key.bin"
|
||||
VERIFICATION_KEY_PATH="../build/verification_key.json"
|
||||
VERIFIER_SOL_PATH="../build/verifier.sol"
|
||||
|
||||
mkdir -p ../build
|
||||
|
||||
if [ ! -f "$CIRCUIT_JSON_PATH" ]; then
|
||||
echo "Downloading circuit.json"
|
||||
wget --quiet $CIRCUIT_JSON -O $CIRCUIT_JSON_PATH
|
||||
fi
|
||||
|
||||
if [ ! -f "$PROVING_KEY_BIN_PATH" ]; then
|
||||
echo "Downloading proving_key.bin"
|
||||
wget --quiet $PROVING_KEY_BIN -O $PROVING_KEY_BIN_PATH
|
||||
fi
|
||||
|
||||
if [ ! -f "$VERIFICATION_KEY_PATH" ]; then
|
||||
echo "Downloading verification_key.json"
|
||||
wget --quiet $VERIFICATION_KEY_JSON -O $VERIFICATION_KEY_PATH
|
||||
fi
|
||||
|
||||
if [ ! -f "$VERIFIER_SOL_PATH" ]; then
|
||||
echo "Downloading verifier.sol"
|
||||
wget --quiet $VERIFIER_SOL -O $VERIFIER_SOL_PATH
|
||||
fi
|
||||
6
circuits/scripts/runTestsInCircleCi.sh
Executable file
6
circuits/scripts/runTestsInCircleCi.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash -xe
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
cd ..
|
||||
|
||||
npm run test-blake2s
|
||||
51
circuits/ts/__tests__/blake2s/Blake2s.test.ts
Normal file
51
circuits/ts/__tests__/blake2s/Blake2s.test.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(1000000)
|
||||
|
||||
import * as path from 'path'
|
||||
import * as snarkjs from 'snarkjs'
|
||||
const bigInt = snarkjs.bigInt
|
||||
import * as crypto from 'crypto'
|
||||
import * as compiler from 'circom'
|
||||
|
||||
describe('Blake2s should match test vectors', () => {
|
||||
test('Should compile mixing_g', async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, 'mixing_g_test.circom'))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log('Vars: '+circuit.nVars)
|
||||
console.log('Constraints: '+circuit.nConstraints)
|
||||
|
||||
})
|
||||
|
||||
test('Should compile blake2s_compression', async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, 'blake2s_compression_test.circom'))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log('Vars: '+circuit.nVars)
|
||||
console.log('Constraints: '+circuit.nConstraints)
|
||||
})
|
||||
|
||||
test('Should run blake2s', async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, 'blake2s_test.circom'))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log('Vars: '+circuit.nVars)
|
||||
console.log('Constraints: '+circuit.nConstraints)
|
||||
|
||||
const bits = '11111111'
|
||||
const inputs = {}
|
||||
for (let i = 0; i < bits.length; i++) {
|
||||
inputs[`in_bits[${i}]`] = bigInt(bits[i])
|
||||
}
|
||||
const witness = circuit.calculateWitness(inputs)
|
||||
|
||||
let coeff = bigInt(1)
|
||||
let result = bigInt(0)
|
||||
for (let i = 0; i < 256; i++) {
|
||||
result = result.add(bigInt(witness[circuit.getSignalIdx(`main.out[${i}]`)].toString()).mul(coeff))
|
||||
coeff = coeff.shl(1)
|
||||
}
|
||||
console.log(`blake2s hash: 0x${result.toString(16)}`)
|
||||
expect(result.toString(16)).toEqual('8a1ef126b4e286703744a80b2f414be700cc93023e7bfc8688b79b54931abd27')
|
||||
})
|
||||
})
|
||||
93
circuits/ts/__tests__/blake2s/Uint32.test.ts
Normal file
93
circuits/ts/__tests__/blake2s/Uint32.test.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(1000000)
|
||||
|
||||
import * as path from 'path'
|
||||
import * as snarkjs from 'snarkjs'
|
||||
const bigInt = snarkjs.bigInt
|
||||
import * as crypto from 'crypto'
|
||||
import * as compiler from 'circom'
|
||||
|
||||
describe("Uint32 test", () => {
|
||||
test("Should add 5 Uint32s", async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, "uint32_add_test.circom"))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log("Vars: "+circuit.nVars)
|
||||
console.log("Constraints: "+circuit.nConstraints)
|
||||
|
||||
const inputs = {}
|
||||
|
||||
const nums = [4294967295, 4294967294, 4294967293, 4294967292, 4294967291]
|
||||
for (let i = 0; i < nums.length; i++) {
|
||||
let num = bigInt(nums[i])
|
||||
//inputs[`nums_vals[${i}]`] = num
|
||||
for (let j = 0; j < 32; j++) {
|
||||
// this extracts the least significant bit
|
||||
const bit = num.and(bigInt(1))
|
||||
inputs[`nums_bits[${i}][${j}]`] = bit
|
||||
num = num.shr(1)
|
||||
}
|
||||
}
|
||||
const witness = circuit.calculateWitness(inputs)
|
||||
|
||||
let expected_num = nums.reduce((result, current) => {
|
||||
if (result == bigInt(-1)) {
|
||||
result = bigInt(current)
|
||||
} else {
|
||||
result = result.add(bigInt(current))
|
||||
}
|
||||
return result
|
||||
}, bigInt(-1))
|
||||
let expected_bits: number[] = []
|
||||
for (let j = 0; j < 32; j++) {
|
||||
const bit = expected_num.and(bigInt(1))
|
||||
expected_bits.push(bit)
|
||||
expected_num = expected_num.shr(1)
|
||||
}
|
||||
|
||||
for (let i = 0; i < 32; i++) {
|
||||
expect(witness[circuit.getSignalIdx(`main.out_bits[${i}]`)].toString()).toEqual(snarkjs.bigInt(expected_bits[i]).toString())
|
||||
}
|
||||
})
|
||||
|
||||
test("Should xor 2 Uint32s", async () => {
|
||||
const cirDef = await compiler(path.join(__dirname, "uint32_xor_test.circom"))
|
||||
const circuit = new snarkjs.Circuit(cirDef)
|
||||
|
||||
console.log("Vars: "+circuit.nVars)
|
||||
console.log("Constraints: "+circuit.nConstraints)
|
||||
|
||||
const inputs = {}
|
||||
|
||||
const nums = [24959295, 4594067494]
|
||||
for (let i = 0; i < nums.length; i++) {
|
||||
let num = bigInt(nums[i])
|
||||
//inputs[`nums_vals[${i}]`] = num
|
||||
for (let j = 0; j < 32; j++) {
|
||||
// this extracts the least significant bit
|
||||
const bit = num.and(bigInt(1))
|
||||
let var_name
|
||||
if (i == 0) {
|
||||
var_name = 'a'
|
||||
} else {
|
||||
var_name = 'b'
|
||||
}
|
||||
inputs[`${var_name}_bits[${j}]`] = bit
|
||||
num = num.shr(1)
|
||||
}
|
||||
}
|
||||
const witness = circuit.calculateWitness(inputs)
|
||||
|
||||
let expected_num = bigInt(nums[0] ^ nums[1])
|
||||
let expected_bits: number[] = []
|
||||
for (let j = 0; j < 32; j++) {
|
||||
const bit = expected_num.and(bigInt(1))
|
||||
expected_bits.push(bit)
|
||||
expected_num = expected_num.shr(1)
|
||||
}
|
||||
|
||||
for (let i = 0; i < 32; i++) {
|
||||
expect(witness[circuit.getSignalIdx(`main.out_bits[${i}]`)].toString()).toEqual(snarkjs.bigInt(expected_bits[i]).toString())
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,3 @@
|
||||
include "../../../circom/blake2s/blake2s.circom";
|
||||
|
||||
component main = Blake2sCompression(5, 0);
|
||||
3
circuits/ts/__tests__/blake2s/blake2s_test.circom
Normal file
3
circuits/ts/__tests__/blake2s/blake2s_test.circom
Normal file
@@ -0,0 +1,3 @@
|
||||
include "../../../circom/blake2s/blake2s.circom";
|
||||
|
||||
component main = Blake2s(8, 0);
|
||||
@@ -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];
|
||||
3
circuits/ts/__tests__/blake2s/uint32_add_test.circom
Normal file
3
circuits/ts/__tests__/blake2s/uint32_add_test.circom
Normal file
@@ -0,0 +1,3 @@
|
||||
include "../../../circom/blake2s/uint32.circom";
|
||||
|
||||
component main = Uint32Add(5);
|
||||
3
circuits/ts/__tests__/blake2s/uint32_xor_test.circom
Normal file
3
circuits/ts/__tests__/blake2s/uint32_xor_test.circom
Normal file
@@ -0,0 +1,3 @@
|
||||
include "../../../circom/blake2s/uint32.circom";
|
||||
|
||||
component main = Uint32Xor();
|
||||
11
circuits/ts/bin2hex.ts
Normal file
11
circuits/ts/bin2hex.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
const bin = process.argv[2]
|
||||
|
||||
const buf1 = Buffer.alloc(bin.length/8)
|
||||
for (let i = 0; i < buf1.length; i++) {
|
||||
let t = 1
|
||||
for (let j = 0; j < 8; j++) {
|
||||
buf1[i] |= bin[8*i + j] == '1' ? t : 0
|
||||
t *= 2
|
||||
}
|
||||
}
|
||||
console.log(buf1.toString('hex'))
|
||||
10
circuits/ts/hex2bin.ts
Normal file
10
circuits/ts/hex2bin.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
const buf = Buffer.from(process.argv[2], 'hex')
|
||||
let s = ''
|
||||
for (let i = 0; i < buf.length; i++) {
|
||||
let t = 1
|
||||
for (let j = 0; j < 8; j++) {
|
||||
s += ((buf[i] & t) == t) ? '1' : '0'
|
||||
t *= 2
|
||||
}
|
||||
}
|
||||
console.log(s)
|
||||
0
circuits/ts/index.ts
Normal file
0
circuits/ts/index.ts
Normal file
9
circuits/tsconfig.json
Normal file
9
circuits/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": [
|
||||
"./ts"
|
||||
]
|
||||
}
|
||||
7
config/local-dev.yaml
Normal file
7
config/local-dev.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
env: 'local-dev'
|
||||
|
||||
chain:
|
||||
url: "http://localhost:8545"
|
||||
chainId: 1234
|
||||
mnemonic: "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"
|
||||
89
config/package-lock.json
generated
Normal file
89
config/package-lock.json
generated
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"name": "semaphore-config",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "12.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.3.tgz",
|
||||
"integrity": "sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw==",
|
||||
"dev": true
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/config/-/config-3.2.4.tgz",
|
||||
"integrity": "sha512-H1XIGfnU1EAkfjSLn9ZvYDRx9lOezDViuzLDgiJ/lMeqjYe3q6iQfpcLt2NInckJgpAeekbNhQkmnnbdEDs9rw==",
|
||||
"requires": {
|
||||
"json5": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
||||
},
|
||||
"path": {
|
||||
"version": "0.12.7",
|
||||
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
|
||||
"integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=",
|
||||
"requires": {
|
||||
"process": "^0.11.1",
|
||||
"util": "^0.10.3"
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"util": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
||||
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
|
||||
"requires": {
|
||||
"inherits": "2.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
config/package.json
Normal file
17
config/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "semaphore-config",
|
||||
"version": "0.0.1",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"watch": "tsc --watch",
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"config": "^3.1.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"path": "^0.12.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.0.10"
|
||||
}
|
||||
}
|
||||
15
config/ts/index.ts
Normal file
15
config/ts/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as path from 'path'
|
||||
|
||||
// set NODE_CONFIG_DIR
|
||||
|
||||
if (!process.env.hasOwnProperty('NODE_CONFIG_DIR')) {
|
||||
process.env.NODE_CONFIG_DIR = path.join(__dirname, '../')
|
||||
}
|
||||
|
||||
if (!process.env.hasOwnProperty('NODE_ENV')) {
|
||||
process.env.NODE_ENV = 'local-dev'
|
||||
}
|
||||
|
||||
const config = require('config')
|
||||
|
||||
export { config }
|
||||
9
config/tsconfig.json
Normal file
9
config/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": [
|
||||
"./ts"
|
||||
]
|
||||
}
|
||||
32
contracts/jest.config.js
Normal file
32
contracts/jest.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
verbose: true,
|
||||
transform: {
|
||||
"^.+\\.tsx?$": 'ts-jest'
|
||||
},
|
||||
testPathIgnorePatterns: [
|
||||
"/build/",
|
||||
"/node_modules/",
|
||||
],
|
||||
testRegex: '/__tests__/.*\\.test\\.ts$',
|
||||
moduleFileExtensions: [
|
||||
'ts',
|
||||
'tsx',
|
||||
'js',
|
||||
'jsx',
|
||||
'json',
|
||||
'node'
|
||||
],
|
||||
moduleNameMapper: {
|
||||
"^@semaphore-contracts(.*)$": "<rootDir>./$1",
|
||||
},
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
diagnostics: {
|
||||
// Do not fail on TS compilation errors
|
||||
// https://kulshekhar.github.io/ts-jest/user/config/diagnostics#do-not-fail-on-first-error
|
||||
warnOnly: true
|
||||
}
|
||||
}
|
||||
},
|
||||
testEnvironment: 'node'
|
||||
}
|
||||
1
contracts/package-lock.json.REMOVED.git-id
Normal file
1
contracts/package-lock.json.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
a22dd75fdaddd98d93ccf3467f78af832dc94864
|
||||
39
contracts/package.json
Normal file
39
contracts/package.json
Normal 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
19
contracts/scripts/compileSol.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
echo 'Building contracts'
|
||||
|
||||
# Delete old files
|
||||
rm -rf ./compiled/*
|
||||
|
||||
mkdir -p ./compiled/abis
|
||||
|
||||
# Copy the Semaphore contracts from the submodule into solidity/
|
||||
|
||||
npx etherlime compile --solcVersion=native --buildDirectory=compiled --workingDirectory=sol --exportAbi
|
||||
|
||||
# Build the MiMC contract from bytecode
|
||||
node build/buildMiMC.js
|
||||
23
contracts/scripts/runGanache.sh
Executable file
23
contracts/scripts/runGanache.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This mnemonic seed is well-known to the public. If you transfer any ETH to
|
||||
# addreses derived from it, expect it to be swept away.
|
||||
|
||||
# Etherlime's ganache command works differently from ganache-cli. It
|
||||
# concatenates `--count minus 10` new accounts generated from `--mnemonic`. The
|
||||
# first 10 are predefined.
|
||||
npx etherlime ganache --mnemonic "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" --gasLimit=8800000 count=10 --networkId 1234
|
||||
|
||||
#npx ganache-cli -a 10 -m='candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' --gasLimit=8800000 --port 8545 -i 1234
|
||||
|
||||
# ETH accounts from the 'candy maple...' mnemonic
|
||||
#0: 0x627306090abab3a6e1400e9345bc60c78a8bef57
|
||||
#1: 0xf17f52151ebef6c7334fad080c5704d77216b732
|
||||
#2: 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
|
||||
#3: 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
|
||||
#4: 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
|
||||
#5: 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
|
||||
#6: 0x2191ef87e392377ec08e7c08eb105ef5448eced5
|
||||
#7: 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
|
||||
#8: 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
|
||||
#9: 0x5aeda56215b167893e80b4fe645ba6d5bab767de
|
||||
10
contracts/scripts/runTestsInCircleCi.sh
Executable file
10
contracts/scripts/runTestsInCircleCi.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash -xe
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
cd ..
|
||||
|
||||
npm run ganache &
|
||||
sleep 3 &&
|
||||
npm run test-semaphore &&
|
||||
sleep 1 &&
|
||||
npm run test-mt
|
||||
180
contracts/sol/IncrementalMerkleTree.sol
Normal file
180
contracts/sol/IncrementalMerkleTree.sol
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import { SnarkConstants } from "./SnarkConstants.sol";
|
||||
import { MiMC } from "./MiMC.sol";
|
||||
|
||||
contract IncrementalMerkleTree is SnarkConstants {
|
||||
// The maximum tree depth
|
||||
uint8 internal constant MAX_DEPTH = 32;
|
||||
|
||||
// The tree depth
|
||||
uint8 internal treeLevels;
|
||||
|
||||
// The number of inserted leaves
|
||||
uint256 internal nextLeafIndex = 0;
|
||||
|
||||
// The Merkle root
|
||||
uint256 public root;
|
||||
|
||||
// The Merkle path to the leftmost leaf upon initialisation. It *should
|
||||
// not* be modified after it has been set by the `initMerkleTree` function.
|
||||
// Caching these values is essential to efficient appends.
|
||||
uint256[MAX_DEPTH] internal zeros;
|
||||
|
||||
// Allows you to compute the path to the element (but it's not the path to
|
||||
// the elements). Caching these values is essential to efficient appends.
|
||||
uint256[MAX_DEPTH] internal filledSubtrees;
|
||||
|
||||
// Whether the contract has already seen a particular Merkle tree root
|
||||
mapping (uint256 => bool) public rootHistory;
|
||||
|
||||
event LeafInsertion(uint256 indexed leaf, uint256 indexed leafIndex);
|
||||
|
||||
/*
|
||||
* Stores the Merkle root and intermediate values (the Merkle path to the
|
||||
* the first leaf) assuming that all leaves are set to _zeroValue.
|
||||
* @param _treeLevels The number of levels of the tree
|
||||
* @param _zeroValue The value to set for every leaf. Ideally, this should
|
||||
* be a nothing-up-my-sleeve value, so that nobody can
|
||||
* say that the deployer knows the preimage of an empty
|
||||
* leaf.
|
||||
*/
|
||||
constructor(uint8 _treeLevels, uint256 _zeroValue) internal {
|
||||
// Limit the Merkle tree to MAX_DEPTH levels
|
||||
require(
|
||||
_treeLevels > 0 && _treeLevels <= MAX_DEPTH,
|
||||
"IncrementalMerkleTree: _treeLevels must be between 0 and 33"
|
||||
);
|
||||
|
||||
/*
|
||||
To initialise the Merkle tree, we need to calculate the Merkle root
|
||||
assuming that each leaf is the zero value.
|
||||
|
||||
H(H(a,b), H(c,d))
|
||||
/ \
|
||||
H(a,b) H(c,d)
|
||||
/ \ / \
|
||||
a b c d
|
||||
|
||||
`zeros` and `filledSubtrees` will come in handy later when we do
|
||||
inserts or updates. e.g when we insert a value in index 1, we will
|
||||
need to look up values from those arrays to recalculate the Merkle
|
||||
root.
|
||||
*/
|
||||
treeLevels = _treeLevels;
|
||||
|
||||
zeros[0] = _zeroValue;
|
||||
|
||||
uint256 currentZero = _zeroValue;
|
||||
for (uint8 i = 1; i < _treeLevels; i++) {
|
||||
uint256 hashed = hashLeftRight(currentZero, currentZero);
|
||||
zeros[i] = hashed;
|
||||
filledSubtrees[i] = hashed;
|
||||
currentZero = hashed;
|
||||
}
|
||||
|
||||
root = hashLeftRight(currentZero, currentZero);
|
||||
}
|
||||
|
||||
/*
|
||||
* Inserts a leaf into the Merkle tree and updates the root and filled
|
||||
* subtrees.
|
||||
* @param _leaf The value to insert. It must be less than the snark scalar
|
||||
* field or this function will throw.
|
||||
* @return The leaf index.
|
||||
*/
|
||||
function insertLeaf(uint256 _leaf) internal returns (uint256) {
|
||||
require(
|
||||
_leaf < SNARK_SCALAR_FIELD,
|
||||
"IncrementalMerkleTree: insertLeaf argument must be < SNARK_SCALAR_FIELD"
|
||||
);
|
||||
|
||||
uint256 currentIndex = nextLeafIndex;
|
||||
|
||||
uint256 depth = uint256(treeLevels);
|
||||
require(currentIndex < uint256(2) ** depth, "IncrementalMerkleTree: tree is full");
|
||||
|
||||
uint256 currentLevelHash = _leaf;
|
||||
uint256 left;
|
||||
uint256 right;
|
||||
|
||||
for (uint8 i = 0; i < treeLevels; i++) {
|
||||
// if current_index is 5, for instance, over the iterations it will
|
||||
// look like this: 5, 2, 1, 0, 0, 0 ...
|
||||
|
||||
if (currentIndex % 2 == 0) {
|
||||
// For later values of `i`, use the previous hash as `left`, and
|
||||
// the (hashed) zero value for `right`
|
||||
left = currentLevelHash;
|
||||
right = zeros[i];
|
||||
|
||||
filledSubtrees[i] = currentLevelHash;
|
||||
} else {
|
||||
left = filledSubtrees[i];
|
||||
right = currentLevelHash;
|
||||
}
|
||||
|
||||
currentLevelHash = hashLeftRight(left, right);
|
||||
|
||||
// equivalent to currentIndex /= 2;
|
||||
currentIndex >>= 1;
|
||||
}
|
||||
|
||||
root = currentLevelHash;
|
||||
rootHistory[root] = true;
|
||||
|
||||
uint256 n = nextLeafIndex;
|
||||
nextLeafIndex += 1;
|
||||
|
||||
emit LeafInsertion(_leaf, n);
|
||||
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
/*
|
||||
* Concatenates and hashes two `uint256` values (left and right) using
|
||||
* a combination of MiMCSponge and `addmod`.
|
||||
* @param _left The first value
|
||||
* @param _right The second value
|
||||
* @return The uint256 hash of _left and _right
|
||||
*/
|
||||
function hashLeftRight(uint256 _left, uint256 _right) internal pure returns (uint256) {
|
||||
|
||||
// Solidity documentation states:
|
||||
// `addmod(uint x, uint y, uint k) returns (uint)`:
|
||||
// compute (x + y) % k where the addition is performed with arbitrary
|
||||
// precision and does not wrap around at 2**256. Assert that k != 0
|
||||
// starting from version 0.5.0.
|
||||
|
||||
uint256 R = _left;
|
||||
uint256 C = 0;
|
||||
|
||||
(R, C) = MiMC.MiMCSponge(R, 0);
|
||||
|
||||
R = addmod(R, _right, SNARK_SCALAR_FIELD);
|
||||
(R, C) = MiMC.MiMCSponge(R, C);
|
||||
|
||||
return R;
|
||||
}
|
||||
}
|
||||
35
contracts/sol/IncrementalMerkleTreeClient.sol
Normal file
35
contracts/sol/IncrementalMerkleTreeClient.sol
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import { IncrementalMerkleTree } from './IncrementalMerkleTree.sol';
|
||||
|
||||
contract IncrementalMerkleTreeClient is IncrementalMerkleTree{
|
||||
constructor(uint8 _treeLevels, uint256 _zeroValue)
|
||||
IncrementalMerkleTree(_treeLevels, _zeroValue)
|
||||
public {
|
||||
}
|
||||
|
||||
function insertLeafAsClient(uint256 _leaf) public {
|
||||
insertLeaf(_leaf);
|
||||
}
|
||||
}
|
||||
30
contracts/sol/MiMC.sol
Normal file
30
contracts/sol/MiMC.sol
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
library MiMC {
|
||||
// Note that this function could also be called "MiMCFeistel", but we name
|
||||
// it "MiMCSponge" for consistency.
|
||||
function MiMCSponge(uint256 in_xL, uint256 in_xR) pure public
|
||||
returns (uint256 xL, uint256 xR);
|
||||
}
|
||||
|
||||
486
contracts/sol/Semaphore.sol
Normal file
486
contracts/sol/Semaphore.sol
Normal file
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import "./verifier.sol";
|
||||
import { IncrementalMerkleTree } from "./IncrementalMerkleTree.sol";
|
||||
import "./Ownable.sol";
|
||||
|
||||
contract Semaphore is Verifier, IncrementalMerkleTree, Ownable {
|
||||
// The external nullifier helps to prevent double-signalling by the same
|
||||
// user. An external nullifier can be active or deactivated.
|
||||
|
||||
// Each node in the linked list
|
||||
struct ExternalNullifierNode {
|
||||
uint232 next;
|
||||
bool exists;
|
||||
bool isActive;
|
||||
}
|
||||
|
||||
// We store the external nullifiers using a mapping of the form:
|
||||
// enA => { next external nullifier; if enA exists; if enA is active }
|
||||
// Think of it as a linked list.
|
||||
mapping (uint232 => ExternalNullifierNode) public
|
||||
externalNullifierLinkedList;
|
||||
|
||||
uint256 public numExternalNullifiers = 0;
|
||||
|
||||
// First and last external nullifiers for linked list enumeration
|
||||
uint232 public firstExternalNullifier = 0;
|
||||
uint232 public lastExternalNullifier = 0;
|
||||
|
||||
// Whether broadcastSignal() can only be called by the owner of this
|
||||
// contract. This is the case as a safe default.
|
||||
bool public isBroadcastPermissioned = true;
|
||||
|
||||
// Whether the contract has already seen a particular nullifier hash
|
||||
mapping (uint256 => bool) public nullifierHashHistory;
|
||||
|
||||
event PermissionSet(bool indexed newPermission);
|
||||
event ExternalNullifierAdd(uint232 indexed externalNullifier);
|
||||
event ExternalNullifierChangeStatus(
|
||||
uint232 indexed externalNullifier,
|
||||
bool indexed active
|
||||
);
|
||||
|
||||
// This value should be equal to
|
||||
// 0x7d10c03d1f7884c85edee6353bd2b2ffbae9221236edde3778eac58089912bc0
|
||||
// which you can calculate using the following ethersjs code:
|
||||
// ethers.utils.solidityKeccak256(['bytes'], [ethers.utils.toUtf8Bytes('Semaphore')])
|
||||
// By setting the value of unset (empty) tree leaves to this
|
||||
// nothing-up-my-sleeve value, the authors hope to demonstrate that they do
|
||||
// not have its preimage and therefore cannot spend funds they do not own.
|
||||
|
||||
uint256 public NOTHING_UP_MY_SLEEVE_ZERO =
|
||||
uint256(keccak256(abi.encodePacked('Semaphore'))) % SNARK_SCALAR_FIELD;
|
||||
|
||||
/*
|
||||
* If broadcastSignal is permissioned, check if msg.sender is the contract
|
||||
* owner
|
||||
*/
|
||||
modifier onlyOwnerIfPermissioned() {
|
||||
require(
|
||||
!isBroadcastPermissioned || isOwner(),
|
||||
"Semaphore: broadcast permission denied"
|
||||
);
|
||||
|
||||
_;
|
||||
}
|
||||
|
||||
/*
|
||||
* @param _treeLevels The depth of the identity tree.
|
||||
* @param _firstExternalNullifier The first identity nullifier to add.
|
||||
*/
|
||||
constructor(uint8 _treeLevels, uint232 _firstExternalNullifier)
|
||||
IncrementalMerkleTree(_treeLevels, NOTHING_UP_MY_SLEEVE_ZERO)
|
||||
Ownable()
|
||||
public {
|
||||
addEn(_firstExternalNullifier, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Registers a new user.
|
||||
* @param _identity_commitment The user's identity commitment, which is the
|
||||
* hash of their public key and their identity
|
||||
* nullifier (a random 31-byte value). It should
|
||||
* be the output of a Pedersen hash. It is the
|
||||
* responsibility of the caller to verify this.
|
||||
*/
|
||||
function insertIdentity(uint256 _identityCommitment) public onlyOwner
|
||||
returns (uint256) {
|
||||
// Ensure that the given identity commitment is not the zero value
|
||||
require(
|
||||
_identityCommitment != NOTHING_UP_MY_SLEEVE_ZERO,
|
||||
"Semaphore: identity commitment cannot be the nothing-up-my-sleeve-value"
|
||||
);
|
||||
|
||||
return insertLeaf(_identityCommitment);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if all values within pi_a, pi_b, and pi_c of a zk-SNARK are less
|
||||
* than the scalar field.
|
||||
* @param _a The corresponding `a` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
* @param _b The corresponding `b` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
* @param _c The corresponding `c` parameter to verifier.sol's
|
||||
verifyProof()
|
||||
*/
|
||||
function areAllValidFieldElements(
|
||||
uint256[8] memory _proof
|
||||
) internal pure returns (bool) {
|
||||
return
|
||||
_proof[0] < SNARK_SCALAR_FIELD &&
|
||||
_proof[1] < SNARK_SCALAR_FIELD &&
|
||||
_proof[2] < SNARK_SCALAR_FIELD &&
|
||||
_proof[3] < SNARK_SCALAR_FIELD &&
|
||||
_proof[4] < SNARK_SCALAR_FIELD &&
|
||||
_proof[5] < SNARK_SCALAR_FIELD &&
|
||||
_proof[6] < SNARK_SCALAR_FIELD &&
|
||||
_proof[7] < SNARK_SCALAR_FIELD;
|
||||
}
|
||||
|
||||
/*
|
||||
* Produces a keccak256 hash of the given signal, shifted right by 8 bits.
|
||||
* @param _signal The signal to hash
|
||||
*/
|
||||
function hashSignal(bytes memory _signal) internal pure returns (uint256) {
|
||||
return uint256(keccak256(_signal)) >> 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenience function which returns a uint256 array of 8 elements which
|
||||
* comprise a Groth16 zk-SNARK proof's pi_a, pi_b, and pi_c values.
|
||||
* @param _a The corresponding `a` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
* @param _b The corresponding `b` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
* @param _c The corresponding `c` parameter to verifier.sol's
|
||||
* verifyProof()
|
||||
*/
|
||||
function packProof (
|
||||
uint256[2] memory _a,
|
||||
uint256[2][2] memory _b,
|
||||
uint256[2] memory _c
|
||||
) public pure returns (uint256[8] memory) {
|
||||
|
||||
return [
|
||||
_a[0],
|
||||
_a[1],
|
||||
_b[0][0],
|
||||
_b[0][1],
|
||||
_b[1][0],
|
||||
_b[1][1],
|
||||
_c[0],
|
||||
_c[1]
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* A convenience function which converts an array of 8 elements, generated
|
||||
* by packProof(), into a format which verifier.sol's verifyProof()
|
||||
* accepts.
|
||||
* @param _proof The proof elements.
|
||||
*/
|
||||
function unpackProof(
|
||||
uint256[8] memory _proof
|
||||
) public pure returns (
|
||||
uint256[2] memory,
|
||||
uint256[2][2] memory,
|
||||
uint256[2] memory
|
||||
) {
|
||||
|
||||
return (
|
||||
[_proof[0], _proof[1]],
|
||||
[
|
||||
[_proof[2], _proof[3]],
|
||||
[_proof[4], _proof[5]]
|
||||
],
|
||||
[_proof[6], _proof[7]]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* A convenience view function which helps operators to easily verify all
|
||||
* inputs to broadcastSignal() using a single contract call. This helps
|
||||
* them to save gas by detecting invalid inputs before they invoke
|
||||
* broadcastSignal(). Note that this function does the same checks as
|
||||
* `isValidSignalAndProof` but returns a bool instead of using require()
|
||||
* statements.
|
||||
* @param _signal The signal to broadcast
|
||||
* @param _proof The proof elements.
|
||||
* @param _root The Merkle tree root
|
||||
* @param _nullifiersHash The nullifiers hash
|
||||
* @param _signalHash The signal hash. This is included so as to verify in
|
||||
* Solidity that the signal hash computed off-chain
|
||||
* matches.
|
||||
* @param _externalNullifier The external nullifier
|
||||
*/
|
||||
function preBroadcastCheck (
|
||||
bytes memory _signal,
|
||||
uint256[8] memory _proof,
|
||||
uint256 _root,
|
||||
uint256 _nullifiersHash,
|
||||
uint256 _signalHash,
|
||||
uint232 _externalNullifier
|
||||
) public view returns (bool) {
|
||||
|
||||
uint256[4] memory publicSignals =
|
||||
[_root, _nullifiersHash, _signalHash, _externalNullifier];
|
||||
|
||||
(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c) =
|
||||
unpackProof(_proof);
|
||||
|
||||
return nullifierHashHistory[_nullifiersHash] == false &&
|
||||
hashSignal(_signal) == _signalHash &&
|
||||
_signalHash == hashSignal(_signal) &&
|
||||
isExternalNullifierActive(_externalNullifier) &&
|
||||
rootHistory[_root] &&
|
||||
areAllValidFieldElements(_proof) &&
|
||||
_root < SNARK_SCALAR_FIELD &&
|
||||
_nullifiersHash < SNARK_SCALAR_FIELD &&
|
||||
verifyProof(a, b, c, publicSignals);
|
||||
}
|
||||
|
||||
/*
|
||||
* A modifier which ensures that the signal and proof are valid.
|
||||
* @param _signal The signal to broadcast
|
||||
* @param _proof The proof elements.
|
||||
* @param _root The Merkle tree root
|
||||
* @param _nullifiersHash The nullifiers hash
|
||||
* @param _signalHash The signal hash
|
||||
* @param _externalNullifier The external nullifier
|
||||
*/
|
||||
modifier isValidSignalAndProof (
|
||||
bytes memory _signal,
|
||||
uint256[8] memory _proof,
|
||||
uint256 _root,
|
||||
uint256 _nullifiersHash,
|
||||
uint232 _externalNullifier
|
||||
) {
|
||||
// Check whether each element in _proof is a valid field element. Even
|
||||
// if verifier.sol does this check too, it is good to do so here for
|
||||
// the sake of good protocol design.
|
||||
require(
|
||||
areAllValidFieldElements(_proof),
|
||||
"Semaphore: invalid field element(s) in proof"
|
||||
);
|
||||
|
||||
// Check whether the nullifier hash has been seen
|
||||
require(
|
||||
nullifierHashHistory[_nullifiersHash] == false,
|
||||
"Semaphore: nullifier already seen"
|
||||
);
|
||||
|
||||
// Check whether the nullifier hash is active
|
||||
require(
|
||||
isExternalNullifierActive(_externalNullifier),
|
||||
"Semaphore: external nullifier not found"
|
||||
);
|
||||
|
||||
// Check whether the given Merkle root has been seen previously
|
||||
require(rootHistory[_root], "Semaphore: root not seen");
|
||||
|
||||
uint256 signalHash = hashSignal(_signal);
|
||||
|
||||
// Check whether _nullifiersHash is a valid field element.
|
||||
require(
|
||||
_nullifiersHash < SNARK_SCALAR_FIELD,
|
||||
"Semaphore: the nullifiers hash must be lt the snark scalar field"
|
||||
);
|
||||
|
||||
uint256[4] memory publicSignals =
|
||||
[_root, _nullifiersHash, signalHash, _externalNullifier];
|
||||
|
||||
(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c) =
|
||||
unpackProof(_proof);
|
||||
|
||||
require(
|
||||
verifyProof(a, b, c, publicSignals),
|
||||
"Semaphore: invalid proof"
|
||||
);
|
||||
|
||||
// Note that we don't need to check if signalHash is less than
|
||||
// SNARK_SCALAR_FIELD because it always holds true due to the
|
||||
// definition of hashSignal()
|
||||
|
||||
_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Broadcasts the signal.
|
||||
* @param _signal The signal to broadcast
|
||||
* @param _proof The proof elements.
|
||||
* @param _root The root of the Merkle tree (the 1st public signal)
|
||||
* @param _nullifiersHash The nullifiers hash (the 2nd public signal)
|
||||
* @param _externalNullifier The nullifiers hash (the 4th public signal)
|
||||
*/
|
||||
function broadcastSignal(
|
||||
bytes memory _signal,
|
||||
uint256[8] memory _proof,
|
||||
uint256 _root,
|
||||
uint256 _nullifiersHash,
|
||||
uint232 _externalNullifier
|
||||
) public
|
||||
onlyOwnerIfPermissioned
|
||||
isValidSignalAndProof(
|
||||
_signal, _proof, _root, _nullifiersHash, _externalNullifier
|
||||
)
|
||||
{
|
||||
// Client contracts should be responsible for storing the signal and/or
|
||||
// emitting it as an event
|
||||
|
||||
// Store the nullifiers hash to prevent double-signalling
|
||||
nullifierHashHistory[_nullifiersHash] = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* A private helper function which adds an external nullifier.
|
||||
* @param _externalNullifier The external nullifier to add.
|
||||
* @param _isFirst Whether _externalNullifier is the first external
|
||||
* nullifier. Only the constructor should set _isFirst to true when it
|
||||
* calls addEn().
|
||||
*/
|
||||
function addEn(uint232 _externalNullifier, bool isFirst) private {
|
||||
if (isFirst) {
|
||||
firstExternalNullifier = _externalNullifier;
|
||||
} else {
|
||||
// The external nullifier must not have already been set
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].exists == false,
|
||||
"Semaphore: external nullifier already set"
|
||||
);
|
||||
|
||||
// Connect the previously added external nullifier node to this one
|
||||
externalNullifierLinkedList[lastExternalNullifier].next =
|
||||
_externalNullifier;
|
||||
}
|
||||
|
||||
// Add a new external nullifier
|
||||
externalNullifierLinkedList[_externalNullifier].next = 0;
|
||||
externalNullifierLinkedList[_externalNullifier].isActive = true;
|
||||
externalNullifierLinkedList[_externalNullifier].exists = true;
|
||||
|
||||
// Set the last external nullifier to this one
|
||||
lastExternalNullifier = _externalNullifier;
|
||||
|
||||
numExternalNullifiers ++;
|
||||
|
||||
emit ExternalNullifierAdd(_externalNullifier);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds an external nullifier to the contract. This external nullifier is
|
||||
* active once it is added. Only the owner can do this.
|
||||
* @param _externalNullifier The new external nullifier to set.
|
||||
*/
|
||||
function addExternalNullifier(uint232 _externalNullifier) public
|
||||
onlyOwner {
|
||||
addEn(_externalNullifier, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Deactivate an external nullifier. The external nullifier must already be
|
||||
* active for this function to work. Only the owner can do this.
|
||||
* @param _externalNullifier The new external nullifier to deactivate.
|
||||
*/
|
||||
function deactivateExternalNullifier(uint232 _externalNullifier) public
|
||||
onlyOwner {
|
||||
// The external nullifier must already exist
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].exists,
|
||||
"Semaphore: external nullifier not found"
|
||||
);
|
||||
|
||||
// The external nullifier must already be active
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].isActive == true,
|
||||
"Semaphore: external nullifier already deactivated"
|
||||
);
|
||||
|
||||
// Deactivate the external nullifier. Note that we don't change the
|
||||
// value of nextEn.
|
||||
externalNullifierLinkedList[_externalNullifier].isActive = false;
|
||||
|
||||
emit ExternalNullifierChangeStatus(_externalNullifier, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reactivate an external nullifier. The external nullifier must already be
|
||||
* inactive for this function to work. Only the owner can do this.
|
||||
* @param _externalNullifier The new external nullifier to reactivate.
|
||||
*/
|
||||
function reactivateExternalNullifier(uint232 _externalNullifier) public
|
||||
onlyOwner {
|
||||
// The external nullifier must already exist
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].exists,
|
||||
"Semaphore: external nullifier not found"
|
||||
);
|
||||
|
||||
// The external nullifier must already have been deactivated
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].isActive == false,
|
||||
"Semaphore: external nullifier is already active"
|
||||
);
|
||||
|
||||
// Reactivate the external nullifier
|
||||
externalNullifierLinkedList[_externalNullifier].isActive = true;
|
||||
|
||||
emit ExternalNullifierChangeStatus(_externalNullifier, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if and only if the specified external nullifier is active
|
||||
* @param _externalNullifier The specified external nullifier.
|
||||
*/
|
||||
function isExternalNullifierActive(uint232 _externalNullifier) public view
|
||||
returns (bool) {
|
||||
return externalNullifierLinkedList[_externalNullifier].isActive;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the next external nullifier after the specified external
|
||||
* nullifier in the linked list.
|
||||
* @param _externalNullifier The specified external nullifier.
|
||||
*/
|
||||
function getNextExternalNullifier(uint232 _externalNullifier) public view
|
||||
returns (uint232) {
|
||||
|
||||
require(
|
||||
externalNullifierLinkedList[_externalNullifier].exists,
|
||||
"Semaphore: no such external nullifier"
|
||||
);
|
||||
|
||||
uint232 n = externalNullifierLinkedList[_externalNullifier].next;
|
||||
|
||||
require(
|
||||
numExternalNullifiers > 1 && externalNullifierLinkedList[n].exists,
|
||||
"Semaphore: no external nullifier exists after the specified one"
|
||||
);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the number of inserted identity commitments.
|
||||
*/
|
||||
function getNumIdentityCommitments() public view returns (uint256) {
|
||||
return nextLeafIndex;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the `isBroadcastPermissioned` storage variable, which determines
|
||||
* whether broadcastSignal can or cannot be called by only the contract
|
||||
* owner.
|
||||
* @param _newPermission True if the broadcastSignal can only be called by
|
||||
* the contract owner; and False otherwise.
|
||||
*/
|
||||
function setPermissioning(bool _newPermission) public onlyOwner {
|
||||
|
||||
isBroadcastPermissioned = _newPermission;
|
||||
|
||||
emit PermissionSet(_newPermission);
|
||||
}
|
||||
}
|
||||
106
contracts/sol/SemaphoreClient.sol
Normal file
106
contracts/sol/SemaphoreClient.sol
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
import { Semaphore } from './Semaphore.sol';
|
||||
|
||||
contract SemaphoreClient {
|
||||
uint256[] public identityCommitments;
|
||||
|
||||
// A mapping of all signals broadcasted
|
||||
mapping (uint256 => bytes) public signalIndexToSignal;
|
||||
|
||||
// A mapping between signal indices to external nullifiers
|
||||
mapping (uint256 => uint256) public signalIndexToExternalNullifier;
|
||||
|
||||
// The next index of the `signalIndexToSignal` mapping
|
||||
uint256 public nextSignalIndex = 0;
|
||||
|
||||
Semaphore public semaphore;
|
||||
|
||||
event SignalBroadcastByClient(uint256 indexed signalIndex);
|
||||
|
||||
constructor(Semaphore _semaphore) public {
|
||||
semaphore = _semaphore;
|
||||
}
|
||||
|
||||
function getIdentityCommitments() public view returns (uint256 [] memory) {
|
||||
return identityCommitments;
|
||||
}
|
||||
|
||||
function getIdentityCommitment(uint256 _index) public view returns (uint256) {
|
||||
return identityCommitments[_index];
|
||||
}
|
||||
|
||||
function insertIdentityAsClient(uint256 _leaf) public {
|
||||
semaphore.insertIdentity(_leaf);
|
||||
identityCommitments.push(_leaf);
|
||||
}
|
||||
|
||||
function addExternalNullifier(uint232 _externalNullifier) public {
|
||||
semaphore.addExternalNullifier(_externalNullifier);
|
||||
}
|
||||
|
||||
function deactivateExternalNullifier(uint232 _externalNullifier) public {
|
||||
semaphore.deactivateExternalNullifier(_externalNullifier);
|
||||
}
|
||||
|
||||
function reactivateExternalNullifier(uint232 _externalNullifier) public {
|
||||
semaphore.reactivateExternalNullifier(_externalNullifier);
|
||||
}
|
||||
|
||||
function broadcastSignal(
|
||||
bytes memory _signal,
|
||||
uint256[8] memory _proof,
|
||||
uint256 _root,
|
||||
uint256 _nullifiersHash,
|
||||
uint232 _externalNullifier
|
||||
) public {
|
||||
uint256 signalIndex = nextSignalIndex;
|
||||
|
||||
// store the signal
|
||||
signalIndexToSignal[nextSignalIndex] = _signal;
|
||||
|
||||
// map the the signal index to the given external nullifier
|
||||
signalIndexToExternalNullifier[nextSignalIndex] = _externalNullifier;
|
||||
|
||||
// increment the signal index
|
||||
nextSignalIndex ++;
|
||||
|
||||
// broadcast the signal
|
||||
semaphore.broadcastSignal(_signal, _proof, _root, _nullifiersHash, _externalNullifier);
|
||||
|
||||
emit SignalBroadcastByClient(signalIndex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the external nullifier which a signal at _index broadcasted to
|
||||
* @param _index The index to use to look up the signalIndexToExternalNullifier mapping
|
||||
*/
|
||||
function getExternalNullifierBySignalIndex(uint256 _index) public view returns (uint256) {
|
||||
return signalIndexToExternalNullifier[_index];
|
||||
}
|
||||
|
||||
function setPermissioning(bool _newPermission) public {
|
||||
semaphore.setPermissioning(_newPermission);
|
||||
}
|
||||
}
|
||||
27
contracts/sol/SnarkConstants.sol
Normal file
27
contracts/sol/SnarkConstants.sol
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Semaphore - Zero-knowledge signaling on Ethereum
|
||||
* Copyright (C) 2020 Barry WhiteHat <barrywhitehat@protonmail.com>, Kobi
|
||||
* Gurkan <kobigurk@gmail.com> and Koh Wei Jie (contact@kohweijie.com)
|
||||
*
|
||||
* This file is part of Semaphore.
|
||||
*
|
||||
* Semaphore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Semaphore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Semaphore. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
contract SnarkConstants {
|
||||
// The scalar field
|
||||
uint256 internal constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
}
|
||||
231
contracts/sol/verifier.sol
Normal file
231
contracts/sol/verifier.sol
Normal file
@@ -0,0 +1,231 @@
|
||||
// Copyright 2017 Christian Reitwiessner
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// 2019 OKIMS
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
|
||||
library Pairing {
|
||||
|
||||
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
|
||||
struct G1Point {
|
||||
uint256 X;
|
||||
uint256 Y;
|
||||
}
|
||||
|
||||
// Encoding of field elements is: X[0] * z + X[1]
|
||||
struct G2Point {
|
||||
uint256[2] X;
|
||||
uint256[2] Y;
|
||||
}
|
||||
|
||||
/*
|
||||
* @return The negation of p, i.e. p.plus(p.negate()) should be zero.
|
||||
*/
|
||||
function negate(G1Point memory p) internal pure returns (G1Point memory) {
|
||||
|
||||
// The prime q in the base field F_q for G1
|
||||
if (p.X == 0 && p.Y == 0) {
|
||||
return G1Point(0, 0);
|
||||
} else {
|
||||
return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @return The sum of two points of G1
|
||||
*/
|
||||
function plus(
|
||||
G1Point memory p1,
|
||||
G1Point memory p2
|
||||
) internal view returns (G1Point memory r) {
|
||||
|
||||
uint256[4] memory input;
|
||||
input[0] = p1.X;
|
||||
input[1] = p1.Y;
|
||||
input[2] = p2.X;
|
||||
input[3] = p2.Y;
|
||||
bool success;
|
||||
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas, 2000), 6, input, 0xc0, r, 0x60)
|
||||
// Use "invalid" to make gas estimation work
|
||||
switch success case 0 { invalid() }
|
||||
}
|
||||
|
||||
require(success,"pairing-add-failed");
|
||||
}
|
||||
|
||||
/*
|
||||
* @return The product of a point on G1 and a scalar, i.e.
|
||||
* p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
|
||||
* points p.
|
||||
*/
|
||||
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
|
||||
|
||||
uint256[3] memory input;
|
||||
input[0] = p.X;
|
||||
input[1] = p.Y;
|
||||
input[2] = s;
|
||||
bool success;
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas, 2000), 7, input, 0x80, r, 0x60)
|
||||
// Use "invalid" to make gas estimation work
|
||||
switch success case 0 { invalid() }
|
||||
}
|
||||
require (success,"pairing-mul-failed");
|
||||
}
|
||||
|
||||
/* @return The result of computing the pairing check
|
||||
* e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
|
||||
* For example,
|
||||
* pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
|
||||
*/
|
||||
function pairing(
|
||||
G1Point memory a1,
|
||||
G2Point memory a2,
|
||||
G1Point memory b1,
|
||||
G2Point memory b2,
|
||||
G1Point memory c1,
|
||||
G2Point memory c2,
|
||||
G1Point memory d1,
|
||||
G2Point memory d2
|
||||
) internal view returns (bool) {
|
||||
|
||||
G1Point[4] memory p1 = [a1, b1, c1, d1];
|
||||
G2Point[4] memory p2 = [a2, b2, c2, d2];
|
||||
|
||||
uint256 inputSize = 24;
|
||||
uint256[] memory input = new uint256[](inputSize);
|
||||
|
||||
for (uint256 i = 0; i < 4; i++) {
|
||||
uint256 j = i * 6;
|
||||
input[j + 0] = p1[i].X;
|
||||
input[j + 1] = p1[i].Y;
|
||||
input[j + 2] = p2[i].X[0];
|
||||
input[j + 3] = p2[i].X[1];
|
||||
input[j + 4] = p2[i].Y[0];
|
||||
input[j + 5] = p2[i].Y[1];
|
||||
}
|
||||
|
||||
uint256[1] memory out;
|
||||
bool success;
|
||||
|
||||
// solium-disable-next-line security/no-inline-assembly
|
||||
assembly {
|
||||
success := staticcall(sub(gas, 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
|
||||
// Use "invalid" to make gas estimation work
|
||||
switch success case 0 { invalid() }
|
||||
}
|
||||
|
||||
require(success,"pairing-opcode-failed");
|
||||
|
||||
return out[0] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
contract Verifier {
|
||||
|
||||
using Pairing for *;
|
||||
|
||||
uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||
|
||||
struct VerifyingKey {
|
||||
Pairing.G1Point alfa1;
|
||||
Pairing.G2Point beta2;
|
||||
Pairing.G2Point gamma2;
|
||||
Pairing.G2Point delta2;
|
||||
Pairing.G1Point[5] IC;
|
||||
}
|
||||
|
||||
struct Proof {
|
||||
Pairing.G1Point A;
|
||||
Pairing.G2Point B;
|
||||
Pairing.G1Point C;
|
||||
}
|
||||
|
||||
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||
vk.alfa1 = Pairing.G1Point(uint256(19412864166662840704199897530865176132379551878490098268546775855212807511623), uint256(1659653118858063231936623514142744009632862245288902525823221677276210935161));
|
||||
vk.beta2 = Pairing.G2Point([uint256(21642731927166283717597040076624334182856179384944638206172042877912623746964), uint256(10167148937945084930725321596284210462590725859059519349238552658864205847292)], [uint256(1224040963938177736215304045934094846196618140310043882848453181887293487598), uint256(21818148805990469610055209831928752212083869699635755409679256612234566494214)]);
|
||||
vk.gamma2 = Pairing.G2Point([uint256(7084583264208126476050882701870582484117653569260992588151213710616665494315), uint256(21338791141815158629032141160990160038215366570564838558882493662897305673845)], [uint256(20409386432685531109985133572396335050408469502427908880430269312598850603009), uint256(21694931277671378411802527161940275869759588185961914055258162012593400433477)]);
|
||||
vk.delta2 = Pairing.G2Point([uint256(15974226699330331350608610622797702188540365463638301508490182464179306746479), uint256(10800304862034523735970868610105048512813625407055208260881866244104890739413)], [uint256(3115757193545898321493679843214264410358333980282409841160781532582592563749), uint256(20585865237865669840907249885451244426128970908885747090346049467936130099745)]);
|
||||
vk.IC[0] = Pairing.G1Point(uint256(15132217740731663181077706894312753465210500809816534743630331656993829080728), uint256(110196461348215931979632312103651461241391911014889357659299988542624772231));
|
||||
vk.IC[1] = Pairing.G1Point(uint256(10128725078198782996699361178651605009720713215878609701039758932704577595075), uint256(6404467707897071196443816328081887791672216217394045289711692279719912978002));
|
||||
vk.IC[2] = Pairing.G1Point(uint256(10522674726928807143308489533204590506138344033395366996236503798983177262637), uint256(4729387380831061502587298076566203099165040519549972799644442785786542168149));
|
||||
vk.IC[3] = Pairing.G1Point(uint256(17764548214378097040054627843654571919160111811296549132642271073668911279441), uint256(18047600752595374913303954050610046390396466710244400595444056973195374265781));
|
||||
vk.IC[4] = Pairing.G1Point(uint256(7483741608009854810379599415076882430339556147495509532415486196559753628073), uint256(15133497018284592127224880003457371607602553122563204208188040273053737050972));
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* @returns Whether the proof is valid given the hardcoded verifying key
|
||||
* above and the public inputs
|
||||
*/
|
||||
function verifyProof(
|
||||
uint256[2] memory a,
|
||||
uint256[2][2] memory b,
|
||||
uint256[2] memory c,
|
||||
uint256[4] memory input
|
||||
) public view returns (bool r) {
|
||||
|
||||
Proof memory proof;
|
||||
proof.A = Pairing.G1Point(a[0], a[1]);
|
||||
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
|
||||
proof.C = Pairing.G1Point(c[0], c[1]);
|
||||
|
||||
VerifyingKey memory vk = verifyingKey();
|
||||
|
||||
// Compute the linear combination vk_x
|
||||
Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
|
||||
|
||||
// Make sure that proof.A, B, and C are each less than the prime q
|
||||
require(proof.A.X < PRIME_Q, "verifier-aX-gte-prime-q");
|
||||
require(proof.A.Y < PRIME_Q, "verifier-aY-gte-prime-q");
|
||||
|
||||
require(proof.B.X[0] < PRIME_Q, "verifier-bX0-gte-prime-q");
|
||||
require(proof.B.Y[0] < PRIME_Q, "verifier-bY0-gte-prime-q");
|
||||
|
||||
require(proof.B.X[1] < PRIME_Q, "verifier-bX1-gte-prime-q");
|
||||
require(proof.B.Y[1] < PRIME_Q, "verifier-bY1-gte-prime-q");
|
||||
|
||||
require(proof.C.X < PRIME_Q, "verifier-cX-gte-prime-q");
|
||||
require(proof.C.Y < PRIME_Q, "verifier-cY-gte-prime-q");
|
||||
|
||||
// Make sure that every input is less than the snark scalar field
|
||||
for (uint256 i = 0; i < input.length; i++) {
|
||||
require(input[i] < SNARK_SCALAR_FIELD,"verifier-gte-snark-scalar-field");
|
||||
vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
|
||||
}
|
||||
|
||||
vk_x = Pairing.plus(vk_x, vk.IC[0]);
|
||||
|
||||
return Pairing.pairing(
|
||||
Pairing.negate(proof.A),
|
||||
proof.B,
|
||||
vk.alfa1,
|
||||
vk.beta2,
|
||||
vk_x,
|
||||
vk.gamma2,
|
||||
proof.C,
|
||||
vk.delta2
|
||||
);
|
||||
}
|
||||
}
|
||||
220
contracts/ts/__tests__/FixedVerifier.test.ts
Normal file
220
contracts/ts/__tests__/FixedVerifier.test.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(90000)
|
||||
|
||||
const MiMC = require('@semaphore-contracts/compiled/MiMC.json')
|
||||
const Semaphore = require('@semaphore-contracts/compiled/Semaphore.json')
|
||||
const SemaphoreClient = require('@semaphore-contracts/compiled/SemaphoreClient.json')
|
||||
const hasEvent = require('etherlime/cli-commands/etherlime-test/events.js').hasEvent
|
||||
|
||||
import {
|
||||
SnarkBigInt,
|
||||
genIdentity,
|
||||
genIdentityCommitment,
|
||||
genExternalNullifier,
|
||||
genWitness,
|
||||
genCircuit,
|
||||
genProof,
|
||||
genPublicSignals,
|
||||
verifyProof,
|
||||
SnarkProvingKey,
|
||||
SnarkVerifyingKey,
|
||||
parseVerifyingKeyJson,
|
||||
formatForVerifierContract,
|
||||
} from 'libsemaphore'
|
||||
import * as etherlime from 'etherlime-lib'
|
||||
import { config } from 'semaphore-config'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as ethers from 'ethers'
|
||||
|
||||
const NUM_LEVELS = 20
|
||||
const FIRST_EXTERNAL_NULLIFIER = 0
|
||||
const SIGNAL = 'signal0'
|
||||
|
||||
const genTestAccounts = (num: number, mnemonic: string) => {
|
||||
let accounts: ethers.Wallet[] = []
|
||||
|
||||
for (let i=0; i<num; i++) {
|
||||
const p = `m/44'/60'/${i}'/0/0`
|
||||
const wallet = ethers.Wallet.fromMnemonic(mnemonic, p)
|
||||
accounts.push(wallet)
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
const accounts = genTestAccounts(2, config.chain.mnemonic)
|
||||
let semaphoreContract
|
||||
let semaphoreClientContract
|
||||
let mimcContract
|
||||
|
||||
// hex representations of all inserted identity commitments
|
||||
let insertedIdentityCommitments: string[] = []
|
||||
const activeEn = genExternalNullifier(Date.now().toString())
|
||||
const inactiveEn = genExternalNullifier(Date.now().toString())
|
||||
const invalidEn = BigInt(Math.pow(2, 232)).toString()
|
||||
|
||||
let deployer
|
||||
|
||||
describe('Semaphore', () => {
|
||||
beforeAll(async () => {
|
||||
deployer = new etherlime.JSONRPCPrivateKeyDeployer(
|
||||
accounts[0].privateKey,
|
||||
config.get('chain.url'),
|
||||
{
|
||||
gasLimit: 8800000,
|
||||
chainId: config.get('chain.chainId'),
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Deploying MiMC')
|
||||
mimcContract = await deployer.deploy(MiMC, {})
|
||||
|
||||
const libraries = {
|
||||
MiMC: mimcContract.contractAddress,
|
||||
}
|
||||
|
||||
console.log('Deploying Semaphore')
|
||||
semaphoreContract = await deployer.deploy(
|
||||
Semaphore,
|
||||
libraries,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
console.log('Deploying Semaphore Client')
|
||||
semaphoreClientContract = await deployer.deploy(
|
||||
SemaphoreClient,
|
||||
{},
|
||||
semaphoreContract.contractAddress,
|
||||
)
|
||||
|
||||
console.log('Transferring ownership of the Semaphore contract to the Semaphore Client')
|
||||
const tx = await semaphoreContract.transferOwnership(
|
||||
semaphoreClientContract.contractAddress,
|
||||
)
|
||||
|
||||
await tx.wait()
|
||||
})
|
||||
|
||||
test('insert an identity commitment', async () => {
|
||||
const identity = genIdentity()
|
||||
const identityCommitment: SnarkBigInt = genIdentityCommitment(identity)
|
||||
|
||||
const tx = await semaphoreClientContract.insertIdentityAsClient(
|
||||
identityCommitment.toString()
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
|
||||
console.log('Gas used by insertIdentityAsClient():', receipt.gasUsed.toString())
|
||||
|
||||
insertedIdentityCommitments.push('0x' + identityCommitment.toString(16))
|
||||
expect(hasEvent(receipt, semaphoreContract, 'LeafInsertion')).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('signal broadcasts', () => {
|
||||
// Load circuit, proving key, and verifying key
|
||||
const circuitPath = path.join(__dirname, '../../../circuits/build/circuit.json')
|
||||
const provingKeyPath = path.join(__dirname, '../../../circuits/build/proving_key.bin')
|
||||
const verifyingKeyPath = path.join(__dirname, '../../../circuits/build/verification_key.json')
|
||||
|
||||
const cirDef = JSON.parse(fs.readFileSync(circuitPath).toString())
|
||||
const provingKey: SnarkProvingKey = fs.readFileSync(provingKeyPath)
|
||||
const verifyingKey: SnarkVerifyingKey = parseVerifyingKeyJson(fs.readFileSync(verifyingKeyPath).toString())
|
||||
const circuit = genCircuit(cirDef)
|
||||
let identity
|
||||
let identityCommitment
|
||||
let proof
|
||||
let publicSignals
|
||||
let params
|
||||
|
||||
beforeAll(async () => {
|
||||
identity = genIdentity()
|
||||
identityCommitment = genIdentityCommitment(identity)
|
||||
|
||||
await (await semaphoreClientContract.insertIdentityAsClient(identityCommitment.toString())).wait()
|
||||
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
|
||||
const result = await genWitness(
|
||||
SIGNAL,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = formatForVerifierContract(proof, publicSignals)
|
||||
})
|
||||
|
||||
test('the proof should be valid', async () => {
|
||||
expect.assertions(1)
|
||||
const isValid = verifyProof(verifyingKey, proof, publicSignals)
|
||||
expect(isValid).toBeTruthy()
|
||||
})
|
||||
|
||||
test('the pre-broadcast check should pass', async () => {
|
||||
expect.assertions(1)
|
||||
|
||||
const check = await semaphoreContract.preBroadcastCheck(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.a,
|
||||
params.b,
|
||||
params.c,
|
||||
params.input[0],
|
||||
params.input[1],
|
||||
params.input[2],
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
expect(check).toBeTruthy()
|
||||
})
|
||||
|
||||
test('broadcastSignal with an input element above the scalar field should fail', async () => {
|
||||
expect.assertions(1)
|
||||
const size = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617')
|
||||
const oversizedInput = (BigInt(params.input[1]) + size).toString()
|
||||
try {
|
||||
await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.a,
|
||||
params.b,
|
||||
params.c,
|
||||
params.input[0],
|
||||
oversizedInput,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('verifier-gte-snark-scalar-field')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal to active external nullifier with an account with the right permissions should work', async () => {
|
||||
expect.assertions(4)
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.a,
|
||||
params.b,
|
||||
params.c,
|
||||
params.input[0],
|
||||
params.input[1],
|
||||
params.input[3],
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
console.log('Gas used by broadcastSignal():', receipt.gasUsed.toString())
|
||||
|
||||
const index = (await semaphoreClientContract.nextSignalIndex()) - 1
|
||||
const signal = await semaphoreClientContract.signalIndexToSignal(index.toString())
|
||||
|
||||
expect(ethers.utils.toUtf8String(signal)).toEqual(SIGNAL)
|
||||
|
||||
expect(hasEvent(receipt, semaphoreContract, 'SignalBroadcast')).toBeTruthy()
|
||||
expect(hasEvent(receipt, semaphoreClientContract, 'SignalBroadcastByClient')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
130
contracts/ts/__tests__/IncrementalMerkleTree.test.ts
Normal file
130
contracts/ts/__tests__/IncrementalMerkleTree.test.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(90000)
|
||||
|
||||
const MiMC = require('@semaphore-contracts/compiled/MiMC.json')
|
||||
const IncrementalMerkleTreeClient = require('@semaphore-contracts/compiled/IncrementalMerkleTreeClient.json')
|
||||
import * as etherlime from 'etherlime-lib'
|
||||
import { config } from 'semaphore-config'
|
||||
import * as ethers from 'ethers'
|
||||
import { storage, hashers, tree } from 'semaphore-merkle-tree'
|
||||
const mimcSpongeHasher = new hashers.MimcSpongeHasher()
|
||||
|
||||
const account = ethers.Wallet.fromMnemonic(config.chain.mnemonic, `m/44'/60'/0'/0/0`)
|
||||
let deployer
|
||||
let mtContract
|
||||
let mimcContract
|
||||
import {
|
||||
genIdentity,
|
||||
genIdentityCommitment,
|
||||
setupTree,
|
||||
} from 'libsemaphore'
|
||||
|
||||
const LEVELS = 20
|
||||
let tree
|
||||
|
||||
const ZERO_VALUE =
|
||||
ethers.utils.solidityKeccak256(
|
||||
['bytes'],
|
||||
[ethers.utils.toUtf8Bytes('Semaphore')]
|
||||
)
|
||||
|
||||
describe('IncrementalMerkleTree functions should match the semaphore-merkle-tree implementation', () => {
|
||||
let libraries
|
||||
|
||||
beforeAll(async () => {
|
||||
tree = setupTree(LEVELS)
|
||||
|
||||
deployer = new etherlime.JSONRPCPrivateKeyDeployer(
|
||||
account.privateKey,
|
||||
config.get('chain.url'),
|
||||
{
|
||||
gasLimit: 8800000,
|
||||
chainId: config.get('chain.chainId'),
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Deploying MiMC')
|
||||
mimcContract = await deployer.deploy(MiMC, {})
|
||||
|
||||
libraries = {
|
||||
MiMC: mimcContract.contractAddress,
|
||||
}
|
||||
})
|
||||
|
||||
test('deployment', async () => {
|
||||
console.log('Deploying IncrementalMerkleTreeClient')
|
||||
|
||||
mtContract = await deployer.deploy(
|
||||
IncrementalMerkleTreeClient,
|
||||
libraries,
|
||||
LEVELS,
|
||||
ZERO_VALUE,
|
||||
)
|
||||
|
||||
const root = await mtContract.root()
|
||||
const root2 = await tree.root()
|
||||
expect(root.toString()).toEqual(root2)
|
||||
})
|
||||
|
||||
test('deployment should fail if the specified number of levels is 0', async () => {
|
||||
try {
|
||||
await deployer.deploy(
|
||||
IncrementalMerkleTreeClient,
|
||||
libraries,
|
||||
0,
|
||||
ZERO_VALUE,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('IncrementalMerkleTree: _treeLevels must be between 0 and 33')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('initMerkleTree should fail if the specified number of levels exceeds 32', async () => {
|
||||
try {
|
||||
await deployer.deploy(
|
||||
IncrementalMerkleTreeClient,
|
||||
libraries,
|
||||
33,
|
||||
ZERO_VALUE,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('IncrementalMerkleTree: _treeLevels must be between 0 and 33')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('insertLeaf should fail if the leaf > the snark scalar field', async () => {
|
||||
const leaf = '21888242871839275222246405745257275088548364400416034343698204186575808495618'
|
||||
try {
|
||||
await mtContract.insertLeafAsClient(leaf)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('IncrementalMerkleTree: insertLeaf argument must be < SNARK_SCALAR_FIELD'))
|
||||
}
|
||||
})
|
||||
|
||||
test('insertLeaf (via insertLeafAsClient)', async () => {
|
||||
const leaf = genIdentityCommitment(genIdentity()).toString()
|
||||
const tx = await mtContract.insertLeafAsClient(leaf)
|
||||
const receipt = await tx.wait()
|
||||
|
||||
console.log('Gas used by insertLeaf:', receipt.gasUsed.toString())
|
||||
await tree.update(0, leaf)
|
||||
|
||||
const root = await mtContract.root()
|
||||
const root2 = await tree.root()
|
||||
expect(root.toString()).toEqual(root2)
|
||||
})
|
||||
|
||||
test('inserting a few leaves should work', async () => {
|
||||
for (let i = 1; i < 9; i++) {
|
||||
const leaf = genIdentityCommitment(genIdentity()).toString()
|
||||
const tx = await mtContract.insertLeafAsClient(leaf)
|
||||
const receipt = await tx.wait()
|
||||
|
||||
await tree.update(i, leaf)
|
||||
|
||||
const root = await mtContract.root()
|
||||
const root2 = await tree.root()
|
||||
expect(root.toString()).toEqual(root2)
|
||||
}
|
||||
})
|
||||
})
|
||||
581
contracts/ts/__tests__/Semaphore.test.ts
Normal file
581
contracts/ts/__tests__/Semaphore.test.ts
Normal file
@@ -0,0 +1,581 @@
|
||||
require('module-alias/register')
|
||||
jest.setTimeout(90000)
|
||||
|
||||
const MiMC = require('@semaphore-contracts/compiled/MiMC.json')
|
||||
const Semaphore = require('@semaphore-contracts/compiled/Semaphore.json')
|
||||
const SemaphoreClient = require('@semaphore-contracts/compiled/SemaphoreClient.json')
|
||||
const hasEvent = require('etherlime/cli-commands/etherlime-test/events.js').hasEvent
|
||||
|
||||
import {
|
||||
SnarkBigInt,
|
||||
genIdentity,
|
||||
genIdentityCommitment,
|
||||
genExternalNullifier,
|
||||
genWitness,
|
||||
genCircuit,
|
||||
genProof,
|
||||
genPublicSignals,
|
||||
verifyProof,
|
||||
SnarkProvingKey,
|
||||
SnarkVerifyingKey,
|
||||
parseVerifyingKeyJson,
|
||||
genBroadcastSignalParams,
|
||||
genSignalHash,
|
||||
} from 'libsemaphore'
|
||||
import * as etherlime from 'etherlime-lib'
|
||||
import { config } from 'semaphore-config'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as ethers from 'ethers'
|
||||
|
||||
const NUM_LEVELS = 20
|
||||
const FIRST_EXTERNAL_NULLIFIER = 0
|
||||
const SIGNAL = 'signal0'
|
||||
|
||||
const genTestAccounts = (num: number, mnemonic: string) => {
|
||||
let accounts: ethers.Wallet[] = []
|
||||
|
||||
for (let i=0; i<num; i++) {
|
||||
const p = `m/44'/60'/${i}'/0/0`
|
||||
const wallet = ethers.Wallet.fromMnemonic(mnemonic, p)
|
||||
accounts.push(wallet)
|
||||
}
|
||||
|
||||
return accounts
|
||||
}
|
||||
|
||||
const accounts = genTestAccounts(2, config.chain.mnemonic)
|
||||
let semaphoreContract
|
||||
let semaphoreClientContract
|
||||
let mimcContract
|
||||
|
||||
// hex representations of all inserted identity commitments
|
||||
let insertedIdentityCommitments: string[] = []
|
||||
const activeEn = genExternalNullifier('1111')
|
||||
const inactiveEn = genExternalNullifier('2222')
|
||||
|
||||
let deployer
|
||||
|
||||
describe('Semaphore', () => {
|
||||
beforeAll(async () => {
|
||||
deployer = new etherlime.JSONRPCPrivateKeyDeployer(
|
||||
accounts[0].privateKey,
|
||||
config.get('chain.url'),
|
||||
{
|
||||
gasLimit: 8800000,
|
||||
chainId: config.get('chain.chainId'),
|
||||
},
|
||||
)
|
||||
|
||||
console.log('Deploying MiMC')
|
||||
mimcContract = await deployer.deploy(MiMC, {})
|
||||
|
||||
const libraries = {
|
||||
MiMC: mimcContract.contractAddress,
|
||||
}
|
||||
|
||||
console.log('Deploying Semaphore')
|
||||
semaphoreContract = await deployer.deploy(
|
||||
Semaphore,
|
||||
libraries,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
console.log('Deploying Semaphore Client')
|
||||
semaphoreClientContract = await deployer.deploy(
|
||||
SemaphoreClient,
|
||||
{},
|
||||
semaphoreContract.contractAddress,
|
||||
)
|
||||
|
||||
console.log('Transferring ownership of the Semaphore contract to the Semaphore Client')
|
||||
const tx = await semaphoreContract.transferOwnership(
|
||||
semaphoreClientContract.contractAddress,
|
||||
)
|
||||
|
||||
await tx.wait()
|
||||
})
|
||||
|
||||
test('Semaphore belongs to the correct owner', async () => {
|
||||
const owner = await semaphoreContract.owner()
|
||||
expect(owner).toEqual(semaphoreClientContract.contractAddress)
|
||||
})
|
||||
|
||||
test('insert an identity commitment', async () => {
|
||||
const identity = genIdentity()
|
||||
const identityCommitment: SnarkBigInt = genIdentityCommitment(identity)
|
||||
|
||||
const tx = await semaphoreClientContract.insertIdentityAsClient(
|
||||
identityCommitment.toString()
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
|
||||
const numInserted = await semaphoreContract.getNumIdentityCommitments()
|
||||
expect(numInserted.toString()).toEqual('1')
|
||||
|
||||
console.log('Gas used by insertIdentityAsClient():', receipt.gasUsed.toString())
|
||||
|
||||
insertedIdentityCommitments.push('0x' + identityCommitment.toString(16))
|
||||
expect(hasEvent(receipt, semaphoreContract, 'LeafInsertion')).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('identity insertions', () => {
|
||||
test('should be stored in the contract and retrievable via leaves()', async () => {
|
||||
expect.assertions(insertedIdentityCommitments.length + 1)
|
||||
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
expect(leaves.length).toEqual(insertedIdentityCommitments.length)
|
||||
|
||||
const leavesHex = leaves.map(BigInt)
|
||||
|
||||
for (let i = 0; i < insertedIdentityCommitments.length; i++) {
|
||||
const containsLeaf = leavesHex.indexOf(BigInt(insertedIdentityCommitments[i])) > -1
|
||||
expect(containsLeaf).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('should be stored in the contract and retrievable by enumerating leaf()', async () => {
|
||||
expect.assertions(insertedIdentityCommitments.length)
|
||||
|
||||
// Assumes that insertedIdentityCommitments has the same number of
|
||||
// elements as the number of leaves
|
||||
const idCommsBigint = insertedIdentityCommitments.map(BigInt)
|
||||
for (let i = 0; i < insertedIdentityCommitments.length; i++) {
|
||||
const leaf = await semaphoreClientContract.getIdentityCommitment(i)
|
||||
const leafHex = BigInt(leaf.toHexString())
|
||||
expect(idCommsBigint.indexOf(leafHex) > -1).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('inserting an identity commitment of the nothing-up-my-sleeve value should fail', async () => {
|
||||
expect.assertions(1)
|
||||
const nothingUpMySleeve =
|
||||
BigInt(ethers.utils.solidityKeccak256(['bytes'], [ethers.utils.toUtf8Bytes('Semaphore')]))
|
||||
%
|
||||
BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617')
|
||||
|
||||
try {
|
||||
await semaphoreClientContract.insertIdentityAsClient(nothingUpMySleeve.toString())
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: identity commitment cannot be the nothing-up-my-sleeve-value')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('external nullifiers', () => {
|
||||
|
||||
test('when there is only 1 external nullifier, the first and last external nullifier variables should be the same', async () => {
|
||||
expect((await semaphoreContract.numExternalNullifiers()).toNumber()).toEqual(1)
|
||||
const firstEn = await semaphoreContract.firstExternalNullifier()
|
||||
const lastEn = await semaphoreContract.lastExternalNullifier()
|
||||
expect(firstEn.toString()).toEqual(lastEn.toString())
|
||||
})
|
||||
|
||||
test('getNextExternalNullifier should throw if there is only 1 external nullifier', async () => {
|
||||
expect((await semaphoreContract.numExternalNullifiers()).toNumber()).toEqual(1)
|
||||
const firstEn = await semaphoreContract.firstExternalNullifier()
|
||||
try {
|
||||
await semaphoreContract.getNextExternalNullifier(firstEn)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: no external nullifier exists after the specified one')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('should be able to add an external nullifier', async () => {
|
||||
expect.assertions(4)
|
||||
const tx = await semaphoreClientContract.addExternalNullifier(
|
||||
activeEn,
|
||||
{ gasLimit: 200000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
|
||||
expect(receipt.status).toEqual(1)
|
||||
expect(hasEvent(receipt, semaphoreContract, 'ExternalNullifierAdd')).toBeTruthy()
|
||||
|
||||
// Check if isExternalNullifierActive works
|
||||
const isActive = await semaphoreContract.isExternalNullifierActive(activeEn)
|
||||
expect(isActive).toBeTruthy()
|
||||
|
||||
// Check if numExternalNullifiers() returns the correct value
|
||||
expect((await semaphoreContract.numExternalNullifiers()).toNumber()).toEqual(2)
|
||||
})
|
||||
|
||||
test('getNextExternalNullifier should throw if there is no such external nullifier', async () => {
|
||||
try {
|
||||
await semaphoreContract.getNextExternalNullifier('876876876876')
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: no such external nullifier')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('should be able to deactivate an external nullifier', async () => {
|
||||
await (await semaphoreClientContract.addExternalNullifier(
|
||||
inactiveEn,
|
||||
{ gasLimit: 200000 },
|
||||
)).wait()
|
||||
const tx = await semaphoreClientContract.deactivateExternalNullifier(
|
||||
inactiveEn,
|
||||
{ gasLimit: 100000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeFalsy()
|
||||
})
|
||||
|
||||
test('reactivating a deactivated external nullifier and then deactivating it should work', async () => {
|
||||
expect.assertions(3)
|
||||
|
||||
// inactiveEn should be inactive
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeFalsy()
|
||||
|
||||
// reactivate inactiveEn
|
||||
let tx = await semaphoreClientContract.reactivateExternalNullifier(
|
||||
inactiveEn,
|
||||
{ gasLimit: 100000 },
|
||||
)
|
||||
await tx.wait()
|
||||
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeTruthy()
|
||||
|
||||
tx = await semaphoreClientContract.deactivateExternalNullifier(
|
||||
inactiveEn,
|
||||
{ gasLimit: 100000 },
|
||||
)
|
||||
await tx.wait()
|
||||
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeFalsy()
|
||||
})
|
||||
|
||||
test('enumerating external nullifiers should work', async () => {
|
||||
const firstEn = await semaphoreContract.firstExternalNullifier()
|
||||
const lastEn = await semaphoreContract.lastExternalNullifier()
|
||||
|
||||
const externalNullifiers: BigInt[] = [ firstEn ]
|
||||
let currentEn = firstEn
|
||||
|
||||
while (currentEn.toString() !== lastEn.toString()) {
|
||||
currentEn = await semaphoreContract.getNextExternalNullifier(currentEn)
|
||||
externalNullifiers.push(currentEn)
|
||||
}
|
||||
|
||||
expect(externalNullifiers).toHaveLength(3)
|
||||
expect(BigInt(externalNullifiers[0].toString())).toEqual(BigInt(firstEn.toString()))
|
||||
expect(BigInt(externalNullifiers[1].toString())).toEqual(BigInt(activeEn.toString()))
|
||||
expect(BigInt(externalNullifiers[2].toString())).toEqual(BigInt(inactiveEn.toString()))
|
||||
})
|
||||
})
|
||||
|
||||
describe('signal broadcasts', () => {
|
||||
// Load circuit, proving key, and verifying key
|
||||
const circuitPath = path.join(__dirname, '../../../circuits/build/circuit.json')
|
||||
const provingKeyPath = path.join(__dirname, '../../../circuits/build/proving_key.bin')
|
||||
const verifyingKeyPath = path.join(__dirname, '../../../circuits/build/verification_key.json')
|
||||
|
||||
const cirDef = JSON.parse(fs.readFileSync(circuitPath).toString())
|
||||
const provingKey: SnarkProvingKey = fs.readFileSync(provingKeyPath)
|
||||
const verifyingKey: SnarkVerifyingKey = parseVerifyingKeyJson(fs.readFileSync(verifyingKeyPath).toString())
|
||||
const circuit = genCircuit(cirDef)
|
||||
let identity
|
||||
let identityCommitment
|
||||
let proof
|
||||
let publicSignals
|
||||
let params
|
||||
|
||||
beforeAll(async () => {
|
||||
identity = genIdentity()
|
||||
identityCommitment = genIdentityCommitment(identity)
|
||||
|
||||
await (await semaphoreClientContract.insertIdentityAsClient(identityCommitment.toString())).wait()
|
||||
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
|
||||
const result = await genWitness(
|
||||
SIGNAL,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
})
|
||||
|
||||
test('the proof should be valid', async () => {
|
||||
expect.assertions(1)
|
||||
const isValid = verifyProof(verifyingKey, proof, publicSignals)
|
||||
expect(isValid).toBeTruthy()
|
||||
})
|
||||
|
||||
test('the pre-broadcast check should pass', async () => {
|
||||
expect.assertions(1)
|
||||
|
||||
const signal = ethers.utils.toUtf8Bytes(SIGNAL)
|
||||
const check = await semaphoreContract.preBroadcastCheck(
|
||||
signal,
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
genSignalHash(signal).toString(),
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
expect(check).toBeTruthy()
|
||||
})
|
||||
|
||||
test('the pre-broadcast check with an invalid signal should fail', async () => {
|
||||
expect.assertions(1)
|
||||
|
||||
const signal = ethers.utils.toUtf8Bytes(SIGNAL)
|
||||
const check = await semaphoreContract.preBroadcastCheck(
|
||||
'0x0',
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
genSignalHash(signal).toString(),
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
expect(check).toBeFalsy()
|
||||
})
|
||||
|
||||
test('broadcastSignal with an input element above the scalar field should fail', async () => {
|
||||
expect.assertions(1)
|
||||
const size = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617')
|
||||
const oversizedInput = (BigInt(params.nullifiersHash) + size).toString()
|
||||
try {
|
||||
await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.root,
|
||||
oversizedInput,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: the nullifiers hash must be lt the snark scalar field')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal with an invalid proof_data should fail', async () => {
|
||||
expect.assertions(1)
|
||||
try {
|
||||
await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
[
|
||||
"21888242871839275222246405745257275088548364400416034343698204186575808495617",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
"7",
|
||||
],
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: invalid field element(s) in proof')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal with an unseen root should fail', async () => {
|
||||
expect.assertions(1)
|
||||
try {
|
||||
await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.nullifiersHash, // note that this is delibrately swapped
|
||||
params.root,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: root not seen')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal by an unpermissioned user should fail', async () => {
|
||||
expect.assertions(1)
|
||||
try {
|
||||
await semaphoreContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: broadcast permission denied')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('broadcastSignal to active external nullifier with an account with the right permissions should work', async () => {
|
||||
expect.assertions(3)
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
//params.externalNullifier,
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
console.log('Gas used by broadcastSignal():', receipt.gasUsed.toString())
|
||||
|
||||
const index = (await semaphoreClientContract.nextSignalIndex()) - 1
|
||||
const signal = await semaphoreClientContract.signalIndexToSignal(index.toString())
|
||||
|
||||
expect(ethers.utils.toUtf8String(signal)).toEqual(SIGNAL)
|
||||
|
||||
expect(hasEvent(receipt, semaphoreClientContract, 'SignalBroadcastByClient')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('double-signalling to the same external nullifier should fail', async () => {
|
||||
expect.assertions(1)
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
const newSignal = 'newSignal0'
|
||||
|
||||
const result = await genWitness(
|
||||
newSignal,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
try {
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(newSignal),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
FIRST_EXTERNAL_NULLIFIER,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: nullifier already seen')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('signalling to a different external nullifier should work', async () => {
|
||||
expect.assertions(1)
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
const newSignal = 'newSignal1'
|
||||
|
||||
const result = await genWitness(
|
||||
newSignal,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
activeEn,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(newSignal),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
activeEn,
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
})
|
||||
|
||||
test('broadcastSignal to a deactivated external nullifier should fail', async () => {
|
||||
expect.assertions(2)
|
||||
expect(await semaphoreContract.isExternalNullifierActive(inactiveEn)).toBeFalsy()
|
||||
|
||||
identity = genIdentity()
|
||||
identityCommitment = genIdentityCommitment(identity)
|
||||
|
||||
await (await semaphoreClientContract.insertIdentityAsClient(identityCommitment.toString())).wait()
|
||||
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
|
||||
const result = await genWitness(
|
||||
SIGNAL,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
inactiveEn,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
|
||||
try {
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(SIGNAL),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
inactiveEn,
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: external nullifier not found')).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
test('setPermissioning(false) should allow anyone to broadcast a signal', async () => {
|
||||
expect.assertions(2)
|
||||
const leaves = await semaphoreClientContract.getIdentityCommitments()
|
||||
const newSignal = 'newSignal2'
|
||||
|
||||
const result = await genWitness(
|
||||
newSignal,
|
||||
circuit,
|
||||
identity,
|
||||
leaves,
|
||||
NUM_LEVELS,
|
||||
activeEn,
|
||||
)
|
||||
|
||||
proof = await genProof(result.witness, provingKey)
|
||||
publicSignals = genPublicSignals(result.witness, circuit)
|
||||
params = genBroadcastSignalParams(result, proof, publicSignals)
|
||||
try {
|
||||
await semaphoreContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(newSignal),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
activeEn,
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
} catch (e) {
|
||||
expect(e.message.endsWith('Semaphore: broadcast permission denied')).toBeTruthy()
|
||||
}
|
||||
|
||||
await (await semaphoreClientContract.setPermissioning(false, { gasLimit: 100000 })).wait()
|
||||
|
||||
const tx = await semaphoreClientContract.broadcastSignal(
|
||||
ethers.utils.toUtf8Bytes(newSignal),
|
||||
params.proof,
|
||||
params.root,
|
||||
params.nullifiersHash,
|
||||
activeEn,
|
||||
{ gasLimit: 1000000 },
|
||||
)
|
||||
const receipt = await tx.wait()
|
||||
expect(receipt.status).toEqual(1)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
18
contracts/ts/buildMiMC.ts
Normal file
18
contracts/ts/buildMiMC.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as Artifactor from 'truffle-artifactor'
|
||||
const mimcGenContract = require('circomlib/src/mimcsponge_gencontract.js');
|
||||
const artifactor = new Artifactor('compiled/')
|
||||
const SEED = 'mimcsponge'
|
||||
|
||||
const buildMiMC = async () => {
|
||||
await artifactor.save({
|
||||
contractName: 'MiMC',
|
||||
abi: mimcGenContract.abi,
|
||||
unlinked_binary: mimcGenContract.createCode(SEED, 220),
|
||||
})
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
buildMiMC()
|
||||
}
|
||||
|
||||
export default buildMiMC
|
||||
0
contracts/ts/index.ts
Normal file
0
contracts/ts/index.ts
Normal file
9
contracts/tsconfig.json
Normal file
9
contracts/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build"
|
||||
},
|
||||
"include": [
|
||||
"./ts"
|
||||
]
|
||||
}
|
||||
1
docs/.nojekyll
Normal file
1
docs/.nojekyll
Normal 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
4
docs/FontAwesome/css/font-awesome.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/FontAwesome/fonts/FontAwesome.ttf
Normal file
BIN
docs/FontAwesome/fonts/FontAwesome.ttf
Normal file
Binary file not shown.
BIN
docs/FontAwesome/fonts/fontawesome-webfont.eot
Normal file
BIN
docs/FontAwesome/fonts/fontawesome-webfont.eot
Normal file
Binary file not shown.
2671
docs/FontAwesome/fonts/fontawesome-webfont.svg
Normal file
2671
docs/FontAwesome/fonts/fontawesome-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 434 KiB |
BIN
docs/FontAwesome/fonts/fontawesome-webfont.ttf
Normal file
BIN
docs/FontAwesome/fonts/fontawesome-webfont.ttf
Normal file
Binary file not shown.
BIN
docs/FontAwesome/fonts/fontawesome-webfont.woff
Normal file
BIN
docs/FontAwesome/fonts/fontawesome-webfont.woff
Normal file
Binary file not shown.
BIN
docs/FontAwesome/fonts/fontawesome-webfont.woff2
Normal file
BIN
docs/FontAwesome/fonts/fontawesome-webfont.woff2
Normal file
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
293
docs/about.html
Normal 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 contract’s 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
299
docs/api.html
Normal 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
228
docs/audit.html
Normal 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
79
docs/ayu-highlight.css
Normal 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
605
docs/book.js
Normal 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
7
docs/clipboard.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
234
docs/creditsandresources.html
Normal file
234
docs/creditsandresources.html
Normal 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
484
docs/css/chrome.css
Normal 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
159
docs/css/general.css
Normal 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
54
docs/css/print.css
Normal 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
253
docs/css/variables.css
Normal 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
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
BIN
docs/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
79
docs/highlight.css
Normal file
79
docs/highlight.css
Normal 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
2
docs/highlight.js
Normal file
File diff suppressed because one or more lines are too long
330
docs/howitworks.html
Normal file
330
docs/howitworks.html
Normal 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
293
docs/index.html
Normal 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 contract’s 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
382
docs/libsemaphore.html
Normal 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">["e82cc2b8654705e427df423c6300307a873a2e637028fab3163cf95b18bb172e","a02e517dfb3a4184adaa951d02bfe0fe092d1ee34438721d798db75b8db083","15c6540bf7bddb0616984fccda7e954a0fb5ea4679ac686509dc4bd7ba9c3b"]
|
||||
</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) => x.toString()),
|
||||
}
|
||||
</code></pre>
|
||||
<p><strong><code>stringifyBigInts = (obj: any) => 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) => 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) => 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
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
823
docs/print.html
Normal 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 contract’s 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 && \
|
||||
cd semaphore && \
|
||||
npm i && \
|
||||
npm run bootstrap && \
|
||||
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 && \
|
||||
./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 && \
|
||||
./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 && \
|
||||
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 && \
|
||||
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">["e82cc2b8654705e427df423c6300307a873a2e637028fab3163cf95b18bb172e","a02e517dfb3a4184adaa951d02bfe0fe092d1ee34438721d798db75b8db083","15c6540bf7bddb0616984fccda7e954a0fb5ea4679ac686509dc4bd7ba9c3b"]
|
||||
</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 = <list of leaves>
|
||||
</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">["e82cc2b8654705e427df423c6300307a873a2e637028fab3163cf95b18bb172e","a02e517dfb3a4184adaa951d02bfe0fe092d1ee34438721d798db75b8db083","15c6540bf7bddb0616984fccda7e954a0fb5ea4679ac686509dc4bd7ba9c3b"]
|
||||
</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) => x.toString()),
|
||||
}
|
||||
</code></pre>
|
||||
<p><strong><code>stringifyBigInts = (obj: any) => 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) => 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) => 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
260
docs/quickstart.html
Normal 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 && \
|
||||
cd semaphore && \
|
||||
npm i && \
|
||||
npm run bootstrap && \
|
||||
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 && \
|
||||
./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 && \
|
||||
./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 && \
|
||||
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 && \
|
||||
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
477
docs/searcher.js
Normal 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 = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
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
1
docs/searchindex.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/searchindex.json
Normal file
1
docs/searchindex.json
Normal file
File diff suppressed because one or more lines are too long
104
docs/tomorrow-night.css
Normal file
104
docs/tomorrow-night.css
Normal 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
224
docs/trustedsetup.html
Normal 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
315
docs/usage.html
Normal 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">["e82cc2b8654705e427df423c6300307a873a2e637028fab3163cf95b18bb172e","a02e517dfb3a4184adaa951d02bfe0fe092d1ee34438721d798db75b8db083","15c6540bf7bddb0616984fccda7e954a0fb5ea4679ac686509dc4bd7ba9c3b"]
|
||||
</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 = <list of leaves>
|
||||
</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
1
docs_src/book/.nojekyll
Normal file
@@ -0,0 +1 @@
|
||||
This file makes sure that Github Pages doesn't process mdBook's output.
|
||||
4
docs_src/book/FontAwesome/css/font-awesome.css
vendored
Normal file
4
docs_src/book/FontAwesome/css/font-awesome.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
docs_src/book/FontAwesome/fonts/FontAwesome.ttf
Normal file
BIN
docs_src/book/FontAwesome/fonts/FontAwesome.ttf
Normal file
Binary file not shown.
BIN
docs_src/book/FontAwesome/fonts/fontawesome-webfont.eot
Normal file
BIN
docs_src/book/FontAwesome/fonts/fontawesome-webfont.eot
Normal file
Binary file not shown.
2671
docs_src/book/FontAwesome/fonts/fontawesome-webfont.svg
Normal file
2671
docs_src/book/FontAwesome/fonts/fontawesome-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 434 KiB |
BIN
docs_src/book/FontAwesome/fonts/fontawesome-webfont.ttf
Normal file
BIN
docs_src/book/FontAwesome/fonts/fontawesome-webfont.ttf
Normal file
Binary file not shown.
BIN
docs_src/book/FontAwesome/fonts/fontawesome-webfont.woff
Normal file
BIN
docs_src/book/FontAwesome/fonts/fontawesome-webfont.woff
Normal file
Binary file not shown.
BIN
docs_src/book/FontAwesome/fonts/fontawesome-webfont.woff2
Normal file
BIN
docs_src/book/FontAwesome/fonts/fontawesome-webfont.woff2
Normal file
Binary file not shown.
307
docs_src/book/about.html
Normal file
307
docs_src/book/about.html
Normal 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 contract’s 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
314
docs_src/book/api.html
Normal 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
243
docs_src/book/audit.html
Normal 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>
|
||||
79
docs_src/book/ayu-highlight.css
Normal file
79
docs_src/book/ayu-highlight.css
Normal 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
605
docs_src/book/book.js
Normal 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
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
Reference in New Issue
Block a user