├── .dockerignore ├── .gitattributes ├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE.txt ├── README.md ├── backend ├── Dockerfile ├── jest.config.js ├── package-lock.json ├── package.json ├── schemas │ ├── echo.json │ ├── jsonRpc.json │ └── mix.json ├── scripts │ └── copyVerifyingKey.sh ├── ts │ ├── __tests__ │ │ ├── Mix.test.ts │ │ ├── Server.test.ts │ │ └── utils.ts │ ├── errors.ts │ ├── index.ts │ ├── jsonRpc.ts │ └── routes │ │ ├── echo.ts │ │ ├── index.ts │ │ ├── mix.ts │ │ ├── status.ts │ │ └── utils.ts └── tsconfig.json ├── config ├── config.example.yaml ├── docker.yaml ├── package-lock.json ├── package.json ├── ts │ ├── export-config.ts │ └── index.ts └── tsconfig.json ├── contracts ├── Dockerfile ├── README.md ├── ganachePrivateKeys.json ├── package-lock.json ├── package.json ├── scripts │ ├── buildSolidity.sh │ ├── deploy.sh │ └── runGanache.sh ├── solidity │ ├── HashTester.sol │ ├── IERC20.sol │ ├── MerkleTree.sol │ ├── MerkleTreeLib.sol │ ├── MerkleTreeTester.sol │ ├── Migrations.sol │ ├── Mixer.sol │ ├── MockRelayerRegistry.sol │ ├── Ownable.sol │ ├── SafeMath.sol │ ├── Semaphore.sol │ ├── access │ │ ├── MinterRole.sol │ │ └── Roles.sol │ ├── token │ │ ├── ERC20.sol │ │ ├── ERC20Detailed.sol │ │ ├── ERC20Mintable.sol │ │ └── IERC20.sol │ └── verifier.sol ├── ts │ ├── __tests__ │ │ ├── Mixer.test.ts │ │ ├── TokenMixer.test.ts │ │ ├── index.d.ts │ │ └── utils.ts │ ├── accounts.ts │ ├── buildMiMC.ts │ ├── deploy │ │ └── deploy.ts │ └── index.ts └── tsconfig.json ├── docker └── docker-compose.yml ├── docs └── img │ ├── dev_screens.png │ └── logo.png ├── frontend ├── .terserrc ├── Dockerfile ├── README.md ├── abis │ ├── ERC20-abi.json │ ├── ERC20Detailed-abi.json │ ├── ERC20Mintable-abi.json │ ├── HashTester-abi.json │ ├── IERC20-abi.json │ ├── MerkleTree-abi.json │ ├── MerkleTreeTester-abi.json │ ├── MiMC-abi.json │ ├── Migrations-abi.json │ ├── MinterRole-abi.json │ ├── Mixer-abi.json │ ├── MultipleMerkleTree-abi.json │ ├── Ownable-abi.json │ ├── Pairing-abi.json │ ├── RelayerRegistry-abi.json │ ├── Roles-abi.json │ ├── SafeMath-abi.json │ ├── Semaphore-abi.json │ └── Verifier-abi.json ├── exported_config.json ├── externals │ └── worker_threads.js ├── favicons │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── img │ ├── cat.png │ ├── logo.png │ └── og.png ├── index.html ├── less │ ├── components │ │ ├── index.less │ │ └── walletWidget.less │ ├── constants.less │ ├── index.less │ └── routes │ │ ├── countdown.less │ │ ├── deposit.less │ │ ├── index.less │ │ └── quickWithdraw.less ├── nginx.conf ├── package.json ├── server.js ├── ts │ ├── components │ │ ├── txButton.tsx │ │ ├── txHashMessage.tsx │ │ └── walletWidget.tsx │ ├── errors.ts │ ├── index.tsx │ ├── nav.tsx │ ├── routes │ │ ├── about.tsx │ │ ├── countdown.tsx │ │ ├── deposit.tsx │ │ └── quickWithdraw.tsx │ ├── storage.ts │ ├── utils │ │ ├── fetcher.ts │ │ └── mixAmts.ts │ └── web3 │ │ ├── balance.tsx │ │ ├── deposit.tsx │ │ ├── index.tsx │ │ ├── mixer.tsx │ │ └── quickWithdraw.tsx ├── tsconfig.json └── webpack.config.js ├── lerna.json ├── package-lock.json ├── package.json ├── scripts ├── buildImages.sh ├── downloadSnarks.sh ├── runImages.sh └── serveSnarks.sh ├── tsconfig.json ├── tslint.json └── utils ├── package-lock.json ├── package.json ├── ts └── index.ts └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/build 3 | **/*.log 4 | **/*.md 5 | **/.git 6 | .gitattributes 7 | frontend/dist 8 | frontend/build 9 | frontend/.cache 10 | frontend/server.* 11 | **/package-lock.json 12 | contracts/compiled 13 | contracts/parity 14 | semaphore/docs 15 | semaphore/semaphorejs/blake2sdef.json 16 | kovanPrivateKeys.json 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | default.etcd 2 | # prod config 3 | config/*-prod.yaml 4 | config/*-prod.yml 5 | config/local-dev.yaml 6 | config/local-dev.yml 7 | 8 | kovanPrivateKeys.json 9 | frontend/ts/exported_config.json 10 | 11 | verification_key.json 12 | backend/verification_key.json 13 | contracts/.privateKeys.json 14 | contracts/privateKeys.json 15 | 16 | **/build 17 | 18 | frontend/server.cert 19 | frontend/server.key 20 | 21 | contracts/.etherlime-store/ 22 | contracts/compiled/ 23 | contracts/.outputParameter 24 | frontend/dist/ 25 | frontend/.cache/ 26 | frontend/ts/abis 27 | deployedAddresses.json 28 | node_modules/ 29 | lerna-debug.log 30 | 31 | # Swap 32 | [._]*.s[a-v][a-z] 33 | [._]*.sw[a-p] 34 | [._]s[a-rt-v][a-z] 35 | [._]ss[a-gi-z] 36 | [._]sw[a-p] 37 | 38 | # Session 39 | Session.vim 40 | Sessionx.vim 41 | 42 | # Temporary 43 | .netrwhist 44 | *~ 45 | # Auto-generated tag files 46 | tags 47 | # Persistent undo 48 | [._]*.un~ 49 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "semaphore"] 2 | path = semaphore 3 | url = https://github.com/weijiekoh/semaphore.git 4 | branch= master 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_VERSION=11.14.0 2 | 3 | FROM node:${NODE_VERSION}-stretch AS mixer-build 4 | WORKDIR /mixer 5 | 6 | ARG NODE_ENV 7 | ENV NODE_ENV=$NODE_ENV 8 | 9 | COPY package.json lerna.json tsconfig.json /mixer/ 10 | 11 | RUN npm install --quiet && \ 12 | npm cache clean --force 13 | 14 | COPY scripts /mixer/scripts 15 | COPY semaphore /mixer/semaphore 16 | 17 | RUN cd /mixer/ && \ 18 | ./scripts/downloadSnarks.sh --only-verifier 19 | 20 | RUN mkdir /mixer/contracts && \ 21 | mkdir /mixer/config && \ 22 | mkdir /mixer/utils && \ 23 | mkdir /mixer/backend && \ 24 | mkdir /mixer/frontend 25 | 26 | COPY config/package*.json /mixer/config/ 27 | COPY contracts/package*.json /mixer/contracts/ 28 | COPY utils/package*.json /mixer/utils/ 29 | COPY backend/package*.json /mixer/backend/ 30 | COPY frontend/package*.json /mixer/frontend/ 31 | 32 | RUN npx lerna bootstrap --no-progress 33 | 34 | COPY contracts /mixer/contracts 35 | COPY config /mixer/config 36 | COPY utils /mixer/utils 37 | COPY backend /mixer/backend 38 | COPY frontend /mixer/frontend 39 | 40 | RUN wget https://github.com/ethereum/solidity/releases/download/v0.5.12/solc-static-linux 41 | RUN chmod a+x solc-static-linux && mv solc-static-linux /usr/bin/solc 42 | RUN rm -rf /mixer/frontend/build /mixer/frontend/dist 43 | 44 | RUN npm run build 45 | 46 | RUN echo "Building frontend with NODE_ENV=production" && \ 47 | cd frontend && \ 48 | npm run build && \ 49 | npm run webpack-build 50 | 51 | FROM node:${NODE_VERSION}-stretch AS mixer-base 52 | 53 | COPY --from=mixer-build /mixer/contracts /mixer/contracts 54 | COPY --from=mixer-build /mixer/config /mixer/config 55 | COPY --from=mixer-build /mixer/utils /mixer/utils 56 | COPY --from=mixer-build /mixer/backend /mixer/backend 57 | COPY --from=mixer-build /mixer/frontend /mixer/frontend 58 | 59 | COPY --from=mixer-build /mixer/package.json /mixer/package.json 60 | COPY --from=mixer-build /mixer/lerna.json /mixer/lerna.json 61 | COPY --from=mixer-build /mixer/tsconfig.json /mixer/tsconfig.json 62 | 63 | RUN rm -rf /mixer/contracts/ts/ \ 64 | /mixer/config/ts/ \ 65 | /mixer/utils/ts/ \ 66 | /mixer/backend/ts/ \ 67 | /mixer/frontend/ts/ 68 | 69 | WORKDIR /mixer 70 | 71 | RUN cd contracts && npm uninstall --save-dev && \ 72 | cd ../config && npm uninstall --save-dev && \ 73 | cd ../utils && npm uninstall --save-dev && \ 74 | cd ../backend && npm uninstall --save-dev && \ 75 | cd ../frontend && npm uninstall --save-dev 76 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_VERSION=11.14.0 2 | 3 | #FROM node:${NODE_VERSION}-stretch AS mixer-backend 4 | 5 | #COPY --from=mixer-base /mixer /mixer 6 | 7 | FROM mixer-base AS mixer-backend 8 | 9 | WORKDIR /mixer/backend 10 | 11 | RUN rm -rf /mixer/frontend 12 | 13 | CMD ["node", "build/index.js"] 14 | -------------------------------------------------------------------------------- /backend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | transform: { 4 | "^.+\\.tsx?$": 'ts-jest' 5 | }, 6 | testPathIgnorePatterns: [ 7 | "/build/", 8 | "/node_modules/", 9 | ], 10 | testRegex: '/__tests__/.*\\.test\\.ts$', 11 | moduleFileExtensions: [ 12 | 'ts', 13 | 'tsx', 14 | 'js', 15 | 'jsx', 16 | 'json', 17 | 'node' 18 | ], 19 | moduleNameMapper: { 20 | "^@mixer-backend(.*)$": "../backend/$1", 21 | }, 22 | globals: { 23 | 'ts-jest': { 24 | diagnostics: { 25 | // Do not fail on TS compilation errors 26 | // https://kulshekhar.github.io/ts-jest/user/config/diagnostics#do-not-fail-on-first-error 27 | warnOnly: true 28 | } 29 | } 30 | }, 31 | testEnvironment: 'node' 32 | } 33 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mixer-backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "watch": "tsc --watch", 8 | "server": "node build/index.js", 9 | "server-debug": "node --inspect-brk build/index.js", 10 | "test": "NODE_ENV=local-dev jest --testPathPattern=__tests__/", 11 | "test-debug": "NODE_ENV=local-dev node --inspect-brk ./node_modules/.bin/jest --testPathPattern=__tests__/", 12 | "test-mix": "NODE_ENV=local-dev jest --testPathPattern=__tests__/Mix.test.ts", 13 | "test-mix-debug": "NODE_ENV=local-dev node --inspect-brk ./node_modules/.bin/jest --testPathPattern=__tests__/Mix.test.ts", 14 | "build": "./scripts/copyVerifyingKey.sh && tsc" 15 | }, 16 | "author": "Koh Wei Jie", 17 | "license": "GPL-3.0-or-later", 18 | "_moduleAliases": { 19 | "@mixer-backend": "." 20 | }, 21 | "dependencies": { 22 | "ajv": "^6.10.0", 23 | "ethers": "^4.0.30", 24 | "koa": "^2.7.0", 25 | "koa-bodyparser": "^4.2.1", 26 | "koa-helmet": "^4.2.0", 27 | "mixer-contracts": "1.0.0", 28 | "libsemaphore": "^0.0.16", 29 | "mixer-config": "1.0.0", 30 | "mixer-utils": "1.0.0", 31 | "mixer-config": "1.0.0", 32 | "module-alias": "^2.2.0", 33 | "node-etcd-lock": "^0.3.3", 34 | "verror": "^1.10.0" 35 | }, 36 | "devDependencies": { 37 | "@types/jest": "^24.0.15", 38 | "@types/node": "^12.0.12", 39 | "axios": "^0.19.0", 40 | "snarkjs": "^0.1.13", 41 | "jest": "^24.8.0", 42 | "ts-jest": "^24.0.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backend/schemas/echo.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "An echo request for testing", 3 | "decription": "Parameters for a request to the test mixer_echo JSON-RPC method", 4 | "required": ["message"], 5 | "additionalProperties": false, 6 | "properties": { 7 | "message": { 8 | "type": ["string", "number"] 9 | } 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /backend/schemas/jsonRpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-06/schema#", 3 | "description": "A JSON RPC 2.0 request", 4 | "oneOf": [ 5 | { 6 | "description": "An individual request", 7 | "$ref": "#/definitions/request" 8 | }, 9 | { 10 | "description": "An array of requests", 11 | "type": "array", 12 | "items": { "$ref": "#/definitions/request" } 13 | } 14 | ], 15 | "definitions": { 16 | "request": { 17 | "type": "object", 18 | "required": [ "jsonrpc", "method" ], 19 | "properties": { 20 | "jsonrpc": { "enum": [ "2.0" ] }, 21 | "method": { 22 | "type": "string" 23 | }, 24 | "id": { 25 | "type": [ "string", "number", "null" ] 26 | }, 27 | "params": { 28 | "type": [ "array", "object" ] 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/schemas/mix.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "mix", 3 | "decription": "Parameters for a request to the test mixer_mix JSON-RPC method", 4 | "required": [ 5 | "signal", 6 | "a", 7 | "b", 8 | "c", 9 | "input", 10 | "recipientAddress", 11 | "fee" 12 | ], 13 | "additionalProperties": false, 14 | "properties": { 15 | "signal": { 16 | "$ref": "#/definitions/bytes32" 17 | }, 18 | "a": { 19 | "$ref": "#/definitions/twoHexStrs" 20 | }, 21 | "b": { 22 | "type": "array", 23 | "items": { "$ref": "#/definitions/twoHexStrs" }, 24 | "minItems": 2, 25 | "maxItems": 2 26 | }, 27 | "c": { 28 | "$ref": "#/definitions/twoHexStrs" 29 | }, 30 | "input": { 31 | "$ref": "#/definitions/publicInputHexStrs" 32 | }, 33 | "recipientAddress": { 34 | "$ref": "#/definitions/address" 35 | }, 36 | "fee": { 37 | "$ref": "#/definitions/hex" 38 | } 39 | }, 40 | "definitions": { 41 | "bytes32": { 42 | "type": "string", 43 | "pattern": "^0x([A-Fa-f0-9]{64})$" 44 | }, 45 | "address": { 46 | "type": "string", 47 | "pattern": "^0x([A-Fa-f0-9]{40})$" 48 | }, 49 | "hex": { 50 | "type": "string", 51 | "pattern": "^0x([A-Fa-f0-9]+)$" 52 | }, 53 | "twoHexStrs": { 54 | "type": "array", 55 | "items": { "$ref": "#/definitions/hex" }, 56 | "minItems": 2, 57 | "maxItems": 2 58 | }, 59 | "publicInputHexStrs": { 60 | "type": "array", 61 | "items": { "$ref": "#/definitions/hex" }, 62 | "minItems": 4, 63 | "maxItems": 5 64 | } 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /backend/scripts/copyVerifyingKey.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cp ../semaphore/semaphorejs/build/verification_key.json ./ 4 | -------------------------------------------------------------------------------- /backend/ts/__tests__/Server.test.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from '../index' 2 | const Koa = require('koa') 3 | import axios from 'axios' 4 | import * as JsonRpc from '../jsonRpc' 5 | import { config } from 'mixer-config' 6 | import * as errors from '../errors' 7 | import { post } from './utils' 8 | 9 | const PORT = config.get('backend.port') 10 | const HOST = config.get('backend.host') + ':' + PORT.toString() 11 | 12 | const OPTS = { 13 | headers: { 14 | 'Content-Type': 'application/json', 15 | } 16 | } 17 | 18 | let server 19 | 20 | describe('Backend API', () => { 21 | beforeAll(async () => { 22 | const app = createApp() 23 | server = app.listen(PORT) 24 | }) 25 | 26 | test('rejects requests with an incorrect content-type header', async () => { 27 | expect.assertions(1) 28 | 29 | try { 30 | const resp = await axios.post( 31 | HOST, 32 | {}, 33 | { 34 | headers: { 35 | 'Content-Type': 'application/x-www-form-urlencoded', 36 | } 37 | }, 38 | ) 39 | } catch (err) { 40 | expect(err.response.status).toEqual(400) 41 | } 42 | }) 43 | 44 | test('rejects requests with an incorrect HTTP request method', async () => { 45 | expect.assertions(1) 46 | 47 | try { 48 | const resp = await axios.get(HOST) 49 | } catch (err) { 50 | expect(err.response.status).toEqual(405) 51 | } 52 | }) 53 | 54 | test('rejects requests with invalid JSON', async () => { 55 | const resp = await axios.post( 56 | HOST, 57 | '[[[', 58 | { 59 | headers: { 60 | 'Content-Type': 'text/plain', 61 | } 62 | }, 63 | ) 64 | 65 | expect(resp.status).toEqual(200) 66 | expect(resp.data.error).toBeDefined() 67 | expect(resp.data.error).toEqual(JsonRpc.Errors.parseError) 68 | }) 69 | 70 | test('rejects requests with an invalid JSON-RPC 2.0 request', async () => { 71 | const resp = await axios.post( 72 | HOST, 73 | { hello: 'world' }, 74 | OPTS, 75 | ) 76 | 77 | expect(resp.status).toEqual(200) 78 | expect(resp.data.error).toBeDefined() 79 | expect(resp.data.error).toEqual(JsonRpc.Errors.invalidRequest) 80 | }) 81 | 82 | test('handles the echo method', async () => { 83 | const message = 'hello' 84 | const resp = await post(1, 'mixer_echo', { message }) 85 | 86 | expect(resp.status).toEqual(200) 87 | expect(resp.data.result.message).toEqual(message) 88 | }) 89 | 90 | test('handles the echo method in batch', async () => { 91 | let data: JsonRpc.Request[] = [] 92 | for (let i=0; i<5; i++) { 93 | data.push({ 94 | id: i, 95 | jsonrpc: '2.0', 96 | method: 'mixer_echo', 97 | params: { 98 | message: i, 99 | } 100 | }) 101 | } 102 | 103 | const resp = await axios.post( 104 | HOST, 105 | data, 106 | OPTS, 107 | ) 108 | 109 | expect(resp.status).toEqual(200) 110 | expect(resp.data.length).toEqual(data.length) 111 | const expected = JSON.stringify( 112 | [ 113 | { jsonrpc: '2.0', id: 0, result: { message: 0 } }, 114 | { jsonrpc: '2.0', id: 1, result: { message: 1 } }, 115 | { jsonrpc: '2.0', id: 2, result: { message: 2 } }, 116 | { jsonrpc: '2.0', id: 3, result: { message: 3 } }, 117 | { jsonrpc: '2.0', id: 4, result: { message: 4 } }, 118 | ] 119 | ) 120 | expect(JSON.stringify(resp.data)).toEqual(expected) 121 | }) 122 | 123 | test('correct error handling by the echo method', async () => { 124 | const resp = await post(1, 'mixer_echo', { message: '' }) 125 | 126 | expect(resp.status).toEqual(200) 127 | expect(resp.data.error.code).toEqual(errors.errorCodes.BACKEND_ECHO_MSG_BLANK) 128 | expect(resp.data.error.data.name).toEqual('BACKEND_ECHO_MSG_BLANK') 129 | }) 130 | 131 | afterAll(async () => { 132 | server.close() 133 | }) 134 | }) 135 | -------------------------------------------------------------------------------- /backend/ts/__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import * as snarkjs from 'snarkjs' 3 | import * as JsonRpc from '../jsonRpc' 4 | import { config } from 'mixer-config' 5 | 6 | const PORT = config.get('backend.port') 7 | const HOST = config.get('backend.host') + ':' + PORT.toString() 8 | 9 | const OPTS = { 10 | headers: { 11 | 'Content-Type': 'application/json', 12 | } 13 | } 14 | 15 | const post = (id: JsonRpc.Id, method: string, params: any) => { 16 | return axios.post( 17 | HOST, 18 | { 19 | jsonrpc: '2.0', 20 | id, 21 | method, 22 | params, 23 | }, 24 | OPTS, 25 | ) 26 | } 27 | 28 | export { 29 | post, 30 | } 31 | -------------------------------------------------------------------------------- /backend/ts/errors.ts: -------------------------------------------------------------------------------- 1 | import { VError } from 'verror' 2 | 3 | enum MixerErrorNames { 4 | BACKEND_ECHO_MSG_BLANK = 'BACKEND_ECHO_MSG_BLANK', 5 | BACKEND_MIX_PROOF_INVALID = 'BACKEND_MIX_PROOF_INVALID', 6 | BACKEND_MIX_SIGNAL_INVALID = 'BACKEND_MIX_SIGNAL_INVALID', 7 | BACKEND_MIX_SIGNAL_HASH_INVALID = 'BACKEND_MIX_SIGNAL_HASH_INVALID', 8 | BACKEND_MIX_SIGNAL_AND_SIGNAL_HASH_INVALID = 'BACKEND_MIX_SIGNAL_AND_SIGNAL_HASH_INVALID', 9 | BACKEND_MIX_EXTERNAL_NULLIFIER_INVALID = 'BACKEND_MIX_EXTERNAL_NULLIFIER_INVALID', 10 | BACKEND_MIX_BROADCASTER_ADDRESS_INVALID = 'BACKEND_MIX_BROADCASTER_ADDRESS_INVALID', 11 | BACKEND_MIX_PROOF_PRE_BROADCAST_INVALID = 'BACKEND_MIX_PROOF_PRE_BROADCAST_INVALID', 12 | BACKEND_MIX_INSUFFICIENT_ETH_FEE = 'BACKEND_MIX_INSUFFICIENT_ETH_FEE', 13 | BACKEND_MIX_INSUFFICIENT_TOKEN_FEE = 'BACKEND_MIX_INSUFFICIENT_TOKEN_FEE', 14 | } 15 | 16 | const errorCodes = { 17 | BACKEND_ECHO_MSG_BLANK: -32000, 18 | BACKEND_MIX_PROOF_INVALID: -33000, 19 | BACKEND_MIX_SIGNAL_INVALID: -33001, 20 | BACKEND_MIX_SIGNAL_HASH_INVALID: -33002, 21 | BACKEND_MIX_SIGNAL_AND_SIGNAL_HASH_INVALID: -33003, 22 | BACKEND_MIX_EXTERNAL_NULLIFIER_INVALID: -33004, 23 | BACKEND_MIX_BROADCASTER_ADDRESS_INVALID: -33005, 24 | BACKEND_MIX_PROOF_PRE_BROADCAST_INVALID: -33006, 25 | BACKEND_MIX_INSUFFICIENT_ETH_FEE: -32007, 26 | BACKEND_MIX_INSUFFICIENT_TOKEN_FEE: -32008, 27 | } 28 | 29 | interface MixerError { 30 | name: MixerErrorNames 31 | message: string 32 | cause?: any 33 | } 34 | 35 | /* 36 | * Convenience function to create and return a VError 37 | */ 38 | const genError = ( 39 | name: MixerErrorNames, 40 | message: string, 41 | cause?: any, 42 | ) => { 43 | 44 | return new VError({ 45 | name, 46 | message, 47 | cause 48 | }) 49 | } 50 | 51 | export { 52 | MixerErrorNames, 53 | MixerError, 54 | genError, 55 | errorCodes, 56 | } 57 | -------------------------------------------------------------------------------- /backend/ts/index.ts: -------------------------------------------------------------------------------- 1 | require('module-alias/register') 2 | import * as Ajv from 'ajv' 3 | import * as Koa from 'koa'; 4 | import * as bodyParser from 'koa-bodyparser' 5 | 6 | import * as helmet from 'koa-helmet' 7 | 8 | import { config } from 'mixer-config' 9 | import { router } from './routes' 10 | import * as JsonRpc from './jsonRpc' 11 | 12 | //const ajv = new Ajv({ missingRefs: 'ignore' }) 13 | 14 | const ajv = new Ajv() 15 | ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')) 16 | const jsonRpcSchema = require('@mixer-backend/schemas/jsonRpc.json') 17 | const basicValidate: Ajv.ValidateFunction = ajv.compile(jsonRpcSchema) 18 | 19 | /* 20 | * Validate the request against the basic JSON-RPC 2.0 schema 21 | */ 22 | const validateJsonRpcSchema = async ( 23 | ctx: Koa.Context, 24 | next: Function, 25 | ) => { 26 | 27 | if (basicValidate(JSON.parse(ctx.request.rawBody))) { 28 | await next() 29 | } else { 30 | ctx.type = 'application/json-rpc' 31 | ctx.body = JsonRpc.genErrorResponse( 32 | null, 33 | JsonRpc.Errors.invalidRequest.code, 34 | JsonRpc.Errors.invalidRequest.message, 35 | ) 36 | } 37 | } 38 | 39 | /* 40 | * Middleware to ensure that the request body is valid JSON 41 | */ 42 | const validateJsonParse = async ( 43 | ctx: Koa.Context, 44 | next: Function, 45 | ) => { 46 | try { 47 | JSON.parse(ctx.request.rawBody) 48 | await next() 49 | } catch (err) { 50 | ctx.type = 'application/json-rpc' 51 | ctx.body = JsonRpc.genErrorResponse( 52 | null, 53 | JsonRpc.Errors.parseError.code, 54 | JsonRpc.Errors.parseError.message, 55 | ) 56 | } 57 | } 58 | 59 | /* 60 | * Middleware to ensure that the HTTP Content-Type is 61 | * either application/json-rpc, applicaton/json, or application/jsonrequest 62 | */ 63 | const validateHeaders = async ( 64 | ctx: Koa.Context, 65 | next: Function, 66 | ) => { 67 | const contentType = ctx.request.type 68 | if ( 69 | contentType === 'application/json' || 70 | contentType === 'text/plain' 71 | ) { 72 | await next() 73 | } else { 74 | ctx.throw(400, 'Invalid content-type') 75 | } 76 | } 77 | 78 | /* 79 | * Middleware to ensure that the HTTP method is only POST 80 | */ 81 | const validateMethod = async ( 82 | ctx: Koa.Context, 83 | next: Function, 84 | ) => { 85 | if (ctx.request.method !== 'POST') { 86 | ctx.throw(405, 'Method not allowed') 87 | } else { 88 | await next() 89 | } 90 | } 91 | 92 | /* 93 | * Returns a Koa app 94 | */ 95 | const createApp = () => { 96 | const app = new Koa() 97 | 98 | // Set middleware 99 | app.use(helmet()) 100 | app.use(bodyParser({ 101 | enableTypes: ['json', 'text'], 102 | disableBodyParser: true, 103 | })) 104 | 105 | // Validate basic JSON-RPC 2.0 requirements 106 | app.use(validateMethod) 107 | app.use(validateHeaders) 108 | app.use(validateJsonParse) 109 | app.use(validateJsonRpcSchema) 110 | 111 | // Let the router handle everything else 112 | app.use(router) 113 | 114 | return app 115 | } 116 | 117 | const main = async () => { 118 | const port = config.get('backend.port') 119 | const app = createApp() 120 | app.listen(port) 121 | 122 | console.log('Running server on port', port) 123 | } 124 | 125 | if (require.main === module) { 126 | main() 127 | } 128 | 129 | export { createApp } 130 | -------------------------------------------------------------------------------- /backend/ts/jsonRpc.ts: -------------------------------------------------------------------------------- 1 | type Id = string | number | null 2 | 3 | interface Request { 4 | readonly jsonrpc: string 5 | readonly method: string 6 | readonly params?: any 7 | readonly id: Id 8 | } 9 | 10 | interface ResponseSuccess { 11 | readonly jsonrpc: string 12 | readonly result: any 13 | readonly id: Id 14 | } 15 | 16 | interface JsonRpcError { 17 | readonly code: number 18 | readonly message: string 19 | readonly data?: any 20 | } 21 | 22 | interface ResponseError { 23 | readonly jsonrpc: string 24 | readonly error: JsonRpcError 25 | readonly id: Id 26 | } 27 | 28 | const Errors = { 29 | parseError: { 30 | code: -32700, message: 'Parse error', 31 | }, 32 | invalidRequest: { 33 | code: -32600, message: 'Invalid Request' 34 | }, 35 | methodNotFound: { 36 | code: -32601, message: 'Method not found' 37 | }, 38 | invalidParams: { 39 | code: -32602, message: 'Invalid params' 40 | }, 41 | internalError: { 42 | code: -32603, message: 'Internal error' 43 | }, 44 | } 45 | 46 | type Response = (ResponseSuccess | ResponseError) 47 | 48 | const genSuccessResponse = (id: Id, result: any): ResponseSuccess => { 49 | return { 50 | jsonrpc: '2.0', 51 | id, 52 | result, 53 | } 54 | } 55 | 56 | const genErrorResponse = ( 57 | id: Id, 58 | code: number, 59 | message: string, 60 | data?: any, 61 | ): ResponseError => { 62 | return { 63 | jsonrpc: '2.0', 64 | id, 65 | error: { 66 | code, 67 | message, 68 | data, 69 | }, 70 | } 71 | } 72 | 73 | export { 74 | Id, 75 | Request, 76 | Response, 77 | ResponseSuccess, 78 | ResponseError, 79 | JsonRpcError, 80 | Errors, 81 | genSuccessResponse, 82 | genErrorResponse, 83 | } 84 | -------------------------------------------------------------------------------- /backend/ts/routes/echo.ts: -------------------------------------------------------------------------------- 1 | import * as errors from '../errors' 2 | import { genValidator } from './utils' 3 | 4 | const echo = async ({ message }) => { 5 | if (message !== '') { 6 | return { message } 7 | } else { 8 | const errorMsg = 'the message param cannot be blank' 9 | throw { 10 | code: errors.errorCodes.BACKEND_ECHO_MSG_BLANK, 11 | message: errorMsg, 12 | data: errors.genError( 13 | errors.MixerErrorNames.BACKEND_ECHO_MSG_BLANK, 14 | errorMsg, 15 | ) 16 | } 17 | } 18 | } 19 | 20 | const echoRoute = { 21 | route: echo, 22 | reqValidator: genValidator('echo'), 23 | } 24 | 25 | export default echoRoute 26 | -------------------------------------------------------------------------------- /backend/ts/routes/index.ts: -------------------------------------------------------------------------------- 1 | import * as Koa from 'koa'; 2 | import * as JsonRpc from '../jsonRpc' 3 | import * as Ajv from 'ajv' 4 | import echoRoute from './echo' 5 | import { mixEthRoute, mixTokensRoute } from './mix' 6 | import backendStatusRoute from './status' 7 | import { config } from 'mixer-config' 8 | 9 | interface Route { 10 | reqValidator: Ajv.ValidateFunction 11 | route(bodyData: JsonRpc.Request): Promise 12 | } 13 | 14 | // Define routes here 15 | const routes = { 16 | mixer_mix_eth: mixEthRoute, 17 | mixer_mix_tokens: mixTokensRoute, 18 | //mixer_status: backendStatusRoute, 19 | } 20 | 21 | // Dev-only routes for testing 22 | if (config.get('env') !== 'production') { 23 | routes['mixer_echo'] = echoRoute 24 | } 25 | 26 | // Invoke the route 27 | const handle = async (reqData: JsonRpc.Request) => { 28 | try { 29 | const route = routes[reqData.method] 30 | 31 | if (route.reqValidator(reqData.params)) { 32 | const result = await route.route(reqData.params) 33 | 34 | return JsonRpc.genSuccessResponse(reqData.id, result) 35 | } else { 36 | 37 | return JsonRpc.genErrorResponse( 38 | reqData.id, 39 | JsonRpc.Errors.invalidParams.code, 40 | JsonRpc.Errors.invalidParams.message, 41 | ) 42 | } 43 | } catch (err) { 44 | 45 | return JsonRpc.genErrorResponse( 46 | reqData.id, 47 | err.code, 48 | err.message, 49 | err.data, 50 | ) 51 | } 52 | } 53 | 54 | const router = async ( 55 | ctx: Koa.Context, 56 | _: Function, 57 | ) => { 58 | // Assume that ctx.body is already valid JSON and that it has already been 59 | // validated in a previous middleware layer 60 | const reqData = JSON.parse(ctx.request.rawBody) 61 | 62 | let resData 63 | 64 | // Check whether the request is a batch or single request 65 | if (Array.isArray(reqData)) { 66 | resData = await Promise.all( 67 | reqData.map((data: any) => { 68 | return handle(data) 69 | }) 70 | ) 71 | } else { 72 | resData = await handle(reqData) 73 | } 74 | 75 | ctx.type = 'application/json-rpc' 76 | ctx.body = resData 77 | } 78 | 79 | export { router } 80 | -------------------------------------------------------------------------------- /backend/ts/routes/status.ts: -------------------------------------------------------------------------------- 1 | const backendStatus = async () => { 2 | } 3 | 4 | export default backendStatus 5 | -------------------------------------------------------------------------------- /backend/ts/routes/utils.ts: -------------------------------------------------------------------------------- 1 | require('module-alias/register') 2 | 3 | import * as Ajv from 'ajv' 4 | 5 | const genValidator = ( 6 | name: string, 7 | ) => { 8 | const ajv = new Ajv() 9 | const schema = require(`@mixer-backend/schemas/${name}.json`) 10 | const validate: Ajv.ValidateFunction = ajv.compile(schema) 11 | 12 | return validate 13 | } 14 | 15 | export { genValidator } 16 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./build", 5 | "resolveJsonModule": true 6 | }, 7 | "include": [ 8 | "./ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /config/config.example.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | env: 'local-dev' 3 | mixAmtEth: "0.1" 4 | feeAmtEth: "0.001" 5 | 6 | testing: 7 | mixAmtTokens: 100 8 | feeAmtTokens: 1 9 | 10 | chain: 11 | url: "http://localhost:8545" 12 | chainId: 1234 13 | #url: "https://kovan.infura.io/v3/5d37c494621a43558d77c90e368d4022" 14 | #chainId: 42 15 | mix: 16 | gasLimit: 8000000 17 | privateKeysPath: "../ganachePrivateKeys.json" 18 | deployedAddresses: 19 | MiMC: '0x8CdaF0CD259887258Bc13a92C0a6dA92698644C0' 20 | MultipleMerkleTree: '0xF12b5dd4EAD5F743C6BaA640B0216200e89B60Da' 21 | Semaphore: '0x345cA3e014Aaf5dcA488057592ee47305D9B3e10' 22 | Mixer: '0xf25186B5081Ff5cE73482AD761DB0eB0d25abfBF' 23 | RelayRegistry: '0x2C2B9C9a4a25e24B174f26114e8926a9f2128FE4' 24 | 25 | backend: 26 | port: 3000 27 | host: "http://localhost" 28 | hotWalletPrivKeyPath: "/home/di/MIXER_SECRETS/hotWalletPrivKey.json" 29 | broadcasterAddress: "0x627306090abaB3A6e1400e9345bC60c78a8BEf57" 30 | etcd: 31 | host: "localhost" 32 | port: 2379 33 | lockTime: 7000 34 | testing: 35 | privKeys: 36 | - "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3" 37 | - "0xae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f" 38 | 39 | frontend: 40 | snarks: 41 | paths: 42 | verificationKey: 'http://localhost:8000/build/verification_key.json' 43 | circuit: 'http://localhost:8000/build/circuit.json' 44 | provingKey: 'http://localhost:8000/build/proving_key.bin' 45 | countdown: 46 | endsAtUtcMidnight: false 47 | endsAfterSecs: 5 48 | blockExplorerTxPrefix: "https://kovan.etherscan.io/tx/" 49 | supportedNetworkName: "local" 50 | supportedNetwork: 1234 51 | -------------------------------------------------------------------------------- /config/docker.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | env: 'docker' 3 | mixAmtEth: 0.1 4 | mixAmtTokens: 20 5 | feeAmtEth: 0.001 6 | feeAmtTokens: 2 7 | tokenSym: 'DAI' 8 | tokenDecimals: 18 9 | 10 | chain: 11 | url: "https://kovan.infura.io/v3/5d37c494621a43558d77c90e368d4022" 12 | chainId: 42 13 | mix: 14 | gasLimit: 500000 15 | privateKeysPath: "../kovanPrivateKeys.json" 16 | deployedAddresses: 17 | MiMC: '0x4e8E48FE6D2A6eec138269a92085b1110C8A763D' 18 | Semaphore: '0x341B0D9B7ac18067fF1a99D9265c33Ae29ECdbf7' 19 | Mixer: '0xecaFa9BE8fc203a064aFb04dbe53f98E55ddFBD1' 20 | TokenMixer: '0x12E12EFD0046613F35Ac82406a0b429E16F258dd' 21 | TokenSemaphore: '0x2d789d11E697bF39Ca3d9C07F565C9065DD02542' 22 | RelayerRegistry: '0xb080FCf2c1ABDa24e161AD68D338b4DEF3073d02' 23 | Token: '0xc4375b7de8af5a38a93548eb8453a498222c4ff2' 24 | 25 | backend: 26 | port: 3000 27 | host: "http://localhost" 28 | hotWalletPrivKeyPath: "/run/secrets/hotWalletPrivKeyPath" 29 | relayerAddress: "0x53BE836678eA3Dbf7257ef32954184Ac06C2D4b1" 30 | etcd: 31 | host: "mixer-etcd" 32 | port: 2379 33 | lockTime: 7000 34 | 35 | frontend: 36 | snarks: 37 | paths: 38 | verificationKey: 'https://micromixtest.blob.core.windows.net/snarks/verification_key.json' 39 | provingKey: 'https://micromixtest.blob.core.windows.net/snarks/proving_key.bin' 40 | circuit: 'https://micromixtest.blob.core.windows.net/snarks/circuit.json' 41 | countdown: 42 | endsAtUtcMidnight: true 43 | endsAfterSecs: 5 44 | supportedNetworkName: "Kovan" 45 | supportedNetwork: 42 46 | blockExplorerTxPrefix: "https://kovan.etherscan.io/tx/" 47 | -------------------------------------------------------------------------------- /config/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mixer-config", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "12.7.2", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", 10 | "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", 11 | "dev": true 12 | }, 13 | "argparse": { 14 | "version": "1.0.10", 15 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 16 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 17 | "requires": { 18 | "sprintf-js": "~1.0.2" 19 | } 20 | }, 21 | "config": { 22 | "version": "3.2.2", 23 | "resolved": "https://registry.npmjs.org/config/-/config-3.2.2.tgz", 24 | "integrity": "sha512-rOsfIOAcG82AWouK4/vBS/OKz3UPl2T/kP0irExmXJJOoWg2CmdfPLdx56bCoMUMFNh+7soQkQWCUC8DyemiwQ==", 25 | "requires": { 26 | "json5": "^1.0.1" 27 | } 28 | }, 29 | "esprima": { 30 | "version": "4.0.1", 31 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 32 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 33 | }, 34 | "inherits": { 35 | "version": "2.0.3", 36 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 37 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 38 | }, 39 | "js-yaml": { 40 | "version": "3.13.1", 41 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 42 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 43 | "requires": { 44 | "argparse": "^1.0.7", 45 | "esprima": "^4.0.0" 46 | } 47 | }, 48 | "json5": { 49 | "version": "1.0.1", 50 | "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", 51 | "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", 52 | "requires": { 53 | "minimist": "^1.2.0" 54 | } 55 | }, 56 | "minimist": { 57 | "version": "1.2.0", 58 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 59 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 60 | }, 61 | "path": { 62 | "version": "0.12.7", 63 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 64 | "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", 65 | "requires": { 66 | "process": "^0.11.1", 67 | "util": "^0.10.3" 68 | } 69 | }, 70 | "process": { 71 | "version": "0.11.10", 72 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 73 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" 74 | }, 75 | "sprintf-js": { 76 | "version": "1.0.3", 77 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 78 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" 79 | }, 80 | "util": { 81 | "version": "0.10.4", 82 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", 83 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", 84 | "requires": { 85 | "inherits": "2.0.3" 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mixer-config", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc && node build/export-config.js > ../frontend/exported_config.json", 8 | "watch": "tsc --watch" 9 | }, 10 | "author": "Koh Wei Jie", 11 | "license": "GPL-3.0-or-later", 12 | "dependencies": { 13 | "config": "^3.1.0", 14 | "js-yaml": "^3.13.1", 15 | "path": "^0.12.7" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^12.0.10" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /config/ts/export-config.ts: -------------------------------------------------------------------------------- 1 | import { config } from './index' 2 | 3 | if (require.main === module) { 4 | let c = JSON.parse(JSON.stringify(config)) 5 | if (c.chain.privateKeys) { 6 | delete c.chain.privateKeys 7 | } 8 | if (c.backend.hotWalletPrivKey) { 9 | delete c.backend.hotWalletPrivKey 10 | } 11 | console.log(JSON.stringify(config)) 12 | } 13 | -------------------------------------------------------------------------------- /config/ts/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | 3 | // set NODE_CONFIG_DIR 4 | 5 | if (!process.env.hasOwnProperty('NODE_CONFIG_DIR')) { 6 | process.env.NODE_CONFIG_DIR = path.join(__dirname, '../') 7 | } 8 | 9 | if (!process.env.hasOwnProperty('NODE_ENV')) { 10 | process.env.NODE_ENV = 'local-dev' 11 | } 12 | 13 | const config = require('config') 14 | 15 | export { config } 16 | -------------------------------------------------------------------------------- /config/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./build", 5 | "resolveJsonModule": true 6 | }, 7 | "include": [ 8 | "./ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /contracts/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_VERSION=11.14.0 2 | 3 | FROM node:${NODE_VERSION}-stretch AS mixer-testnet 4 | 5 | COPY --from=mixer-base /mixer /mixer 6 | 7 | WORKDIR /mixer/contracts 8 | 9 | #RUN npm run deploy 10 | 11 | CMD ["npm", "run", "ganache"] 12 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # Mixer contracts 2 | 3 | To build the contracts and tests, run: 4 | 5 | ```bash 6 | npm run build 7 | ``` 8 | 9 | The `solidity/` directory contains all `.sol` files for the Semaphore and Mixer 10 | smart contracts. Note that the `npm run build` command will **copy** the 11 | Semaphore contracts from the `mixer/semaphore/` submodule into `solidity/` in 12 | order for the `solc-js` compiler to build the contracts properly. As such, do 13 | not modify the Semaphore contracts in `solidity/`; instead, do so in the 14 | `mixer/semaphore` submodule. 15 | 16 | The compiled contracts will be in `compiled/` and ABI files will be in `compiled/abis`. 17 | 18 | To compile contract tests while on the fly, run: 19 | 20 | ```bash 21 | npm run watch 22 | ``` 23 | 24 | To run the tests: 25 | 26 | ```bash 27 | npm run ganache 28 | ``` 29 | 30 | And in another terminal, run: 31 | 32 | ```bash 33 | npm run test 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /contracts/ganachePrivateKeys.json: -------------------------------------------------------------------------------- 1 | [ 2 | "0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", 3 | "0xae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f", 4 | "0x0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1", 5 | "0xc88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c", 6 | "0x388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418", 7 | "0x659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63", 8 | "0x82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8", 9 | "0xaa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7", 10 | "0x0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4", 11 | "0x8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5" 12 | ] 13 | -------------------------------------------------------------------------------- /contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mixer-contracts", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc && ./scripts/buildSolidity.sh", 8 | "test": "etherlime test --path=build/__tests__ --skip-compilation --timeout 90000", 9 | "test-debug": "node --inspect-brk ./node_modules/.bin/etherlime test --path=build/__tests__ --skip-compilation --timeout 90000", 10 | "test-mix": "etherlime test --path=build/__tests__/Mixer.test.js --skip-compilation --timeout 90000", 11 | "test-mix-debug": "node --inspect-brk ./node_modules/.bin/etherlime test --path=build/__tests__/Mixer.test.js --skip-compilation --timeout 90000", 12 | "test-mix-tokens": "etherlime test --path=build/__tests__/TokenMixer.test.js --skip-compilation --timeout 90000", 13 | "test-mix-tokens-debug": "node --inspect-brk ./node_modules/.bin/etherlime test --path=build/__tests__/TokenMixer.test.js --skip-compilation --timeout 90000", 14 | "test-mix-stress": "etherlime test --path=build/__tests__/MixerStress.test.js --skip-compilation --timeout 5000000", 15 | "test-mix-stress-debug": "node --inspect-brk ./node_modules/.bin/etherlime test --path=build/__tests__/MixerStress.test.js --skip-compilation --timeout 5000000", 16 | "watch": "tsc --watch", 17 | "deploy": "./scripts/deploy.sh", 18 | "ganache": "./scripts/runGanache.sh" 19 | }, 20 | "author": "Koh Wei Jie", 21 | "license": "GPL-3.0-or-later", 22 | "_moduleAliases": { 23 | "@mixer-contracts": "." 24 | }, 25 | "devDependencies": { 26 | "@types/jest": "^24.0.16", 27 | "argparse": "^1.0.10", 28 | "circomlib": "0.0.13", 29 | "etherlime": "^2.2.4", 30 | "etherlime-lib": "^1.1.0", 31 | "module-alias": "^2.2.0", 32 | "ethers": "^4.0.32", 33 | "ganache-cli": "^6.4.4", 34 | "ganache-core": "^2.6.1", 35 | "mixer-utils": "1.0.0", 36 | "solc": "^0.4.25", 37 | "truffle-artifactor": "^4.0.10" 38 | }, 39 | "dependencies": { 40 | "config": "^3.1.0", 41 | "mixer-config": "1.0.0", 42 | "libsemaphore": "^0.0.16", 43 | "mixer-utils": "1.0.0", 44 | "openzeppelin-solidity": "^2.3.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/scripts/buildSolidity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo 'Building contracts' 5 | 6 | # Delete old files 7 | rm -rf ./compiled/* 8 | 9 | mkdir -p ./compiled/abis 10 | 11 | # Copy the Semaphore contracts from the submodule into solidity/ 12 | cp ../semaphore/semaphorejs/contracts/*.sol solidity/ 13 | cp ../semaphore/semaphorejs/build/verifier.sol solidity/ 14 | 15 | # Compile the contracts 16 | 17 | npx etherlime compile --solcVersion=native --buildDirectory=compiled --workingDirectory=solidity --exportAbi 18 | 19 | # Build the MiMC contract from bytecode 20 | node build/buildMiMC.js 21 | 22 | # Copy ABIs to the frontend module 23 | 24 | mkdir -p ../frontend/ts 25 | cp -r compiled/abis ../frontend/ 26 | -------------------------------------------------------------------------------- /contracts/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | node ./build/deploy/deploy.js -o deployedAddresses.json 4 | cp deployedAddresses.json ../frontend/ts/deployedAddresses.json 5 | cp deployedAddresses.json ../backend/deployedAddresses.json 6 | -------------------------------------------------------------------------------- /contracts/scripts/runGanache.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This mnemonic seed is well-known to the public. If you transfer any ETH to 4 | # addreses derived from it, expect it to be swept away. 5 | 6 | # Etherlime's ganache command works differently from ganache-cli. It 7 | # concatenates `--count minus 10` new accounts generated from `--mnemonic`. The 8 | # first 10 are predefined. 9 | npx etherlime ganache --mnemonic "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" --gasLimit=8800000 count=20 --networkId 1234 10 | 11 | #npx ganache-cli -a 10 -m='candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' --gasLimit=8800000 --port 8545 -i 1234 12 | 13 | # ETH accounts from the 'candy maple...' mnemonic 14 | #0: 0x627306090abab3a6e1400e9345bc60c78a8bef57 15 | #1: 0xf17f52151ebef6c7334fad080c5704d77216b732 16 | #2: 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef 17 | #3: 0x821aea9a577a9b44299b9c15c88cf3087f3b5544 18 | #4: 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2 19 | #5: 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e 20 | #6: 0x2191ef87e392377ec08e7c08eb105ef5448eced5 21 | #7: 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5 22 | #8: 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc 23 | #9: 0x5aeda56215b167893e80b4fe645ba6d5bab767de 24 | -------------------------------------------------------------------------------- /contracts/solidity/HashTester.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * semaphorejs - Zero-knowledge signaling on Ethereum 3 | * Copyright (C) 2019 Kobi Gurkan 4 | * 5 | * This file is part of semaphorejs. 6 | * 7 | * semaphorejs is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * semaphorejs is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with semaphorejs. If not, see . 19 | */ 20 | 21 | pragma solidity ^0.5.0; 22 | 23 | contract HashTester { 24 | constructor() public { 25 | 26 | } 27 | 28 | event DebugHash(bytes32 normal, uint256 converted, bytes32 normal_shifted, uint256 converted_shifted); 29 | event DebugRollingHash(uint256 prev_rolling_hash, uint256 signal_hash, uint256 rolling_hash, bytes encoded); 30 | 31 | uint256 rolling_hash = 1238129381923; 32 | 33 | function Test(bytes memory signal) public { 34 | uint256 signal_hash = uint256(sha256(signal)) >> 8; 35 | emit DebugHash(sha256(signal), uint256(sha256(signal)), sha256(signal) >> 8, signal_hash); 36 | bytes memory encoded = abi.encodePacked(rolling_hash, signal_hash); 37 | uint256 new_rolling_hash = uint256(sha256(encoded)); 38 | emit DebugRollingHash(rolling_hash, signal_hash, new_rolling_hash, encoded); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/solidity/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * Copied from openzeppelin-contracts 2.3.0 5 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 6 | * the optional functions; to access them see `ERC20Detailed`. 7 | */ 8 | interface IERC20 { 9 | /** 10 | * @dev Returns the amount of tokens in existence. 11 | */ 12 | function totalSupply() external view returns (uint256); 13 | 14 | /** 15 | * @dev Returns the amount of tokens owned by `account`. 16 | */ 17 | function balanceOf(address account) external view returns (uint256); 18 | 19 | /** 20 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 21 | * 22 | * Returns a boolean value indicating whether the operation succeeded. 23 | * 24 | * Emits a `Transfer` event. 25 | */ 26 | function transfer(address recipient, uint256 amount) external returns (bool); 27 | 28 | /** 29 | * @dev Returns the remaining number of tokens that `spender` will be 30 | * allowed to spend on behalf of `owner` through `transferFrom`. This is 31 | * zero by default. 32 | * 33 | * This value changes when `approve` or `transferFrom` are called. 34 | */ 35 | function allowance(address owner, address spender) external view returns (uint256); 36 | 37 | /** 38 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 39 | * 40 | * Returns a boolean value indicating whether the operation succeeded. 41 | * 42 | * > Beware that changing an allowance with this method brings the risk 43 | * that someone may use both the old and the new allowance by unfortunate 44 | * transaction ordering. One possible solution to mitigate this race 45 | * condition is to first reduce the spender's allowance to 0 and set the 46 | * desired value afterwards: 47 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 48 | * 49 | * Emits an `Approval` event. 50 | */ 51 | function approve(address spender, uint256 amount) external returns (bool); 52 | 53 | /** 54 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 55 | * allowance mechanism. `amount` is then deducted from the caller's 56 | * allowance. 57 | * 58 | * Returns a boolean value indicating whether the operation succeeded. 59 | * 60 | * Emits a `Transfer` event. 61 | */ 62 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 63 | 64 | /** 65 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 66 | * another (`to`). 67 | * 68 | * Note that `value` may be zero. 69 | */ 70 | event Transfer(address indexed from, address indexed to, uint256 value); 71 | 72 | /** 73 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 74 | * a call to `approve`. `value` is the new allowance. 75 | */ 76 | event Approval(address indexed owner, address indexed spender, uint256 value); 77 | } 78 | -------------------------------------------------------------------------------- /contracts/solidity/MerkleTree.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * semaphorejs - Zero-knowledge signaling on Ethereum 3 | * Copyright (C) 2019 Kobi Gurkan 4 | * 5 | * This file is part of semaphorejs. 6 | * 7 | * semaphorejs is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * semaphorejs is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with semaphorejs. If not, see . 19 | */ 20 | 21 | pragma solidity ^0.5.0; 22 | 23 | library MiMC { 24 | function MiMCSponge(uint256 in_xL, uint256 in_xR, uint256 in_k) pure public returns (uint256 xL, uint256 xR); 25 | } 26 | 27 | contract MerkleTree { 28 | uint8 levels; 29 | 30 | uint256 public root = 0; 31 | uint256[] public filled_subtrees; 32 | uint256[] public zeros; 33 | 34 | uint32 public next_index = 0; 35 | 36 | event LeafAdded(uint256 leaf, uint32 leaf_index); 37 | event LeafUpdated(uint256 leaf, uint32 leaf_index); 38 | 39 | constructor(uint8 tree_levels, uint256 zero_value) public { 40 | levels = tree_levels; 41 | 42 | zeros.push(zero_value); 43 | filled_subtrees.push(zeros[0]); 44 | 45 | for (uint8 i = 1; i < levels; i++) { 46 | zeros.push(HashLeftRight(zeros[i-1], zeros[i-1])); 47 | filled_subtrees.push(zeros[i]); 48 | } 49 | 50 | root = HashLeftRight(zeros[levels - 1], zeros[levels - 1]); 51 | } 52 | 53 | function HashLeftRight(uint256 left, uint256 right) public pure returns (uint256 mimc_hash) { 54 | uint256 k = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 55 | uint256 R = 0; 56 | uint256 C = 0; 57 | 58 | R = addmod(R, left, k); 59 | (R, C) = MiMC.MiMCSponge(R, C, 0); 60 | 61 | R = addmod(R, right, k); 62 | (R, C) = MiMC.MiMCSponge(R, C, 0); 63 | 64 | mimc_hash = R; 65 | } 66 | 67 | function insert(uint256 leaf) internal { 68 | uint32 leaf_index = next_index; 69 | uint32 current_index = next_index; 70 | next_index += 1; 71 | 72 | uint256 current_level_hash = leaf; 73 | uint256 left; 74 | uint256 right; 75 | 76 | for (uint8 i = 0; i < levels; i++) { 77 | if (current_index % 2 == 0) { 78 | left = current_level_hash; 79 | right = zeros[i]; 80 | 81 | filled_subtrees[i] = current_level_hash; 82 | } else { 83 | left = filled_subtrees[i]; 84 | right = current_level_hash; 85 | } 86 | 87 | current_level_hash = HashLeftRight(left, right); 88 | 89 | current_index /= 2; 90 | } 91 | 92 | root = current_level_hash; 93 | 94 | emit LeafAdded(leaf, leaf_index); 95 | } 96 | 97 | function update(uint256 leaf, uint32 leaf_index, uint256[] memory path) internal { 98 | uint32 current_index = leaf_index; 99 | 100 | uint256 current_level_hash = leaf; 101 | uint256 left; 102 | uint256 right; 103 | 104 | for (uint8 i = 0; i < levels; i++) { 105 | if (current_index % 2 == 0) { 106 | left = current_level_hash; 107 | right = path[i]; 108 | } else { 109 | left = path[i]; 110 | right = current_level_hash; 111 | } 112 | 113 | current_level_hash = HashLeftRight(left, right); 114 | 115 | current_index /= 2; 116 | } 117 | 118 | emit LeafUpdated(leaf, leaf_index); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /contracts/solidity/MerkleTreeLib.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * semaphorejs - Zero-knowledge signaling on Ethereum 3 | * Copyright (C) 2019 Kobi Gurkan 4 | * 5 | * This file is part of semaphorejs. 6 | * 7 | * semaphorejs is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * semaphorejs is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with semaphorejs. If not, see . 19 | */ 20 | 21 | pragma solidity ^0.5.0; 22 | 23 | library MiMC { 24 | function MiMCSponge(uint256 in_xL, uint256 in_xR, uint256 in_k) pure public returns (uint256 xL, uint256 xR); 25 | } 26 | 27 | contract MultipleMerkleTree { 28 | uint8[] levels; 29 | 30 | uint256[] internal tree_roots; 31 | uint256[][] filled_subtrees; 32 | uint256[][] zeros; 33 | 34 | uint32[] next_index; 35 | 36 | uint256[][] internal tree_leaves; 37 | 38 | event LeafAdded(uint8 tree_index, uint256 leaf, uint32 leaf_index); 39 | event LeafUpdated(uint8 tree_index, uint256 leaf, uint32 leaf_index); 40 | 41 | function init_tree(uint8 tree_levels, uint256 zero_value) public returns (uint8 tree_index) { 42 | levels.push(tree_levels); 43 | 44 | uint256[] memory current_zeros = new uint256[](tree_levels); 45 | current_zeros[0] = zero_value; 46 | 47 | uint256[] memory current_filled_subtrees = new uint256[](tree_levels); 48 | current_filled_subtrees[0] = current_zeros[0]; 49 | 50 | for (uint8 i = 1; i < tree_levels; i++) { 51 | current_zeros[i] = HashLeftRight(current_zeros[i-1], current_zeros[i-1]); 52 | current_filled_subtrees[i] = current_zeros[i]; 53 | } 54 | 55 | zeros.push(current_zeros); 56 | filled_subtrees.push(current_filled_subtrees); 57 | 58 | tree_roots.push(HashLeftRight(current_zeros[tree_levels - 1], current_zeros[tree_levels - 1])); 59 | next_index.push(0); 60 | 61 | tree_leaves.push(new uint256[](0)); 62 | 63 | return uint8(tree_roots.length) - 1; 64 | } 65 | 66 | function HashLeftRight(uint256 left, uint256 right) public pure returns (uint256 mimc_hash) { 67 | uint256 k = 21888242871839275222246405745257275088548364400416034343698204186575808495617; 68 | uint256 R = 0; 69 | uint256 C = 0; 70 | 71 | R = addmod(R, left, k); 72 | (R, C) = MiMC.MiMCSponge(R, C, 0); 73 | 74 | R = addmod(R, right, k); 75 | (R, C) = MiMC.MiMCSponge(R, C, 0); 76 | 77 | mimc_hash = R; 78 | } 79 | 80 | function insert(uint8 tree_index, uint256 leaf) internal { 81 | uint32 leaf_index = next_index[tree_index]; 82 | uint32 current_index = next_index[tree_index]; 83 | next_index[tree_index] += 1; 84 | 85 | uint256 current_level_hash = leaf; 86 | uint256 left; 87 | uint256 right; 88 | 89 | for (uint8 i = 0; i < levels[tree_index]; i++) { 90 | if (current_index % 2 == 0) { 91 | left = current_level_hash; 92 | right = zeros[tree_index][i]; 93 | 94 | filled_subtrees[tree_index][i] = current_level_hash; 95 | } else { 96 | left = filled_subtrees[tree_index][i]; 97 | right = current_level_hash; 98 | } 99 | 100 | current_level_hash = HashLeftRight(left, right); 101 | 102 | current_index /= 2; 103 | } 104 | 105 | tree_roots[tree_index] = current_level_hash; 106 | 107 | tree_leaves[tree_index].push(leaf); 108 | emit LeafAdded(tree_index, leaf, leaf_index); 109 | } 110 | 111 | function update(uint8 tree_index, uint256 old_leaf, uint256 leaf, uint32 leaf_index, uint256[] memory old_path, uint256[] memory path) internal { 112 | uint32 current_index = leaf_index; 113 | 114 | uint256 current_level_hash = old_leaf; 115 | uint256 left; 116 | uint256 right; 117 | 118 | for (uint8 i = 0; i < levels[tree_index]; i++) { 119 | if (current_index % 2 == 0) { 120 | left = current_level_hash; 121 | right = old_path[i]; 122 | } else { 123 | left = old_path[i]; 124 | right = current_level_hash; 125 | } 126 | 127 | current_level_hash = HashLeftRight(left, right); 128 | 129 | current_index /= 2; 130 | } 131 | 132 | require(tree_roots[tree_index] == current_level_hash, "MultipleMerkleTree: tree root / current level hash mismatch"); 133 | 134 | current_index = leaf_index; 135 | 136 | current_level_hash = leaf; 137 | 138 | for (uint8 i = 0; i < levels[tree_index]; i++) { 139 | if (current_index % 2 == 0) { 140 | left = current_level_hash; 141 | right = path[i]; 142 | } else { 143 | left = path[i]; 144 | right = current_level_hash; 145 | } 146 | 147 | current_level_hash = HashLeftRight(left, right); 148 | 149 | current_index /= 2; 150 | } 151 | 152 | tree_roots[tree_index] = current_level_hash; 153 | 154 | tree_leaves[tree_index][leaf_index] = leaf; 155 | emit LeafUpdated(tree_index, leaf, leaf_index); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /contracts/solidity/MerkleTreeTester.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * semaphorejs - Zero-knowledge signaling on Ethereum 3 | * Copyright (C) 2019 Kobi Gurkan 4 | * 5 | * This file is part of semaphorejs. 6 | * 7 | * semaphorejs is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * semaphorejs is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with semaphorejs. If not, see . 19 | */ 20 | 21 | pragma solidity ^0.5.0; 22 | 23 | import "./MerkleTree.sol"; 24 | 25 | contract MerkleTreeTester is MerkleTree { 26 | constructor() MerkleTree(2, 4) public { 27 | 28 | } 29 | function insert_test(uint256 leaf) public { 30 | insert(leaf); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/solidity/Migrations.sol: -------------------------------------------------------------------------------- 1 | /* 2 | * semaphorejs - Zero-knowledge signaling on Ethereum 3 | * Copyright (C) 2019 Kobi Gurkan 4 | * 5 | * This file is part of semaphorejs. 6 | * 7 | * semaphorejs is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * semaphorejs is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with semaphorejs. If not, see . 19 | */ 20 | 21 | pragma solidity >=0.4.21 <0.6.0; 22 | 23 | contract Migrations { 24 | address public owner; 25 | uint public last_completed_migration; 26 | 27 | constructor() public { 28 | owner = msg.sender; 29 | } 30 | 31 | modifier restricted() { 32 | if (msg.sender == owner) _; 33 | } 34 | 35 | function setCompleted(uint completed) public restricted { 36 | last_completed_migration = completed; 37 | } 38 | 39 | function upgrade(address new_address) public restricted { 40 | Migrations upgraded = Migrations(new_address); 41 | upgraded.setCompleted(last_completed_migration); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/solidity/Mixer.sol: -------------------------------------------------------------------------------- 1 | pragma experimental ABIEncoderV2; 2 | pragma solidity ^0.5.0; 3 | import { Semaphore } from "./Semaphore.sol"; 4 | import { SafeMath } from "./SafeMath.sol"; 5 | import { IERC20 } from "./token/IERC20.sol"; 6 | 7 | /* 8 | * A mixer for either ETH or ERC20 tokens. 9 | * See https://hackmd.io/qlKORn5MSOes1WtsEznu_g for the full specification. 10 | */ 11 | contract Mixer { 12 | using SafeMath for uint256; 13 | 14 | // The amount of ETH or ERC20 tokens to mix at a time. 15 | uint256 public mixAmt; 16 | 17 | // The address of the Semaphore contract. By default, there is one 18 | // Semaphore contract for each Mixer contract. Mixer contracts do not share 19 | // Semaphore contracts. 20 | Semaphore public semaphore; 21 | 22 | // The address of the ERC20 token to mix. If this contract is for raw ETH 23 | // (not wrapped ETH), its value should be `0x0000000000000000000000000000000000000000`. 24 | IERC20 public token; 25 | 26 | event Deposited(address indexed depositor, uint256 indexed mixAmt, uint256 identityCommitment); 27 | event DepositedERC20(address indexed depositor, uint256 indexed mixAmt, uint256 identityCommitment); 28 | event Mixed(address indexed recipient, uint256 indexed mixAmt, uint256 indexed operatorFeeEarned); 29 | event MixedERC20(address indexed recipient, uint256 indexed mixAmt, uint256 indexed operatorFeeEarned); 30 | 31 | // The data structure of a proof that one had previously made a deposit. 32 | // input = [root, nullifiers_hash, signal_hash, external_nullifier, broadcaster_address] 33 | struct DepositProof { 34 | bytes32 signal; 35 | uint[2] a; 36 | uint[2][2] b; 37 | uint[2] c; 38 | uint[4] input; 39 | address payable recipientAddress; 40 | uint256 fee; 41 | } 42 | 43 | /* 44 | * Constructor 45 | * @param _semaphore The address of the Semaphore contract which should 46 | * have been deployed earlier 47 | * @param _mixAmt The amount of Ether a user can mix at a time, in wei 48 | * @param _token The token address if the mixer is for ERC20 tokens, or 49 | * 0x0000... if it is for mixing ETH only. 50 | */ 51 | constructor (address _semaphore, uint256 _mixAmt, address _token) public { 52 | require(_semaphore != address(0), "Mixer: invalid Semaphore address"); 53 | require(_mixAmt != 0, "Mixer: invalid mixAmt"); 54 | 55 | // Set the fixed mixing amount 56 | mixAmt = _mixAmt; 57 | 58 | // Set the Semaphore contract 59 | semaphore = Semaphore(_semaphore); 60 | 61 | // Set the token address 62 | token = IERC20(_token); 63 | } 64 | 65 | /* 66 | * Returns true if the contract only supports mixing ETH, and false if it 67 | * only supports mixing ERC20 tokens. 68 | */ 69 | function supportsEthOnly() public view returns (bool) { 70 | return 0x0000000000000000000000000000000000000000 == address(token); 71 | } 72 | 73 | /* 74 | * Modifier to ensure that a function only runs if this contract mixes ETH. 75 | */ 76 | modifier onlyEth() { 77 | require(supportsEthOnly() == true, "Mixer: only supports ETH"); 78 | _; 79 | } 80 | 81 | /* 82 | * Modifier to ensure that a function only runs if this contract mixes 83 | * ERC20 tokens. 84 | */ 85 | modifier onlyERC20() { 86 | require(supportsEthOnly() == false, "Mixer: only supports tokens"); 87 | _; 88 | } 89 | 90 | /* 91 | * Sets Semaphore's external nullifier to the mixer's address. Call this 92 | * function after transferring Semaphore's ownership to this contract's 93 | * address. 94 | */ 95 | function setSemaphoreExternalNulllifier () public { 96 | semaphore.addExternalNullifier(uint256(address(this))); 97 | } 98 | 99 | /* 100 | * Returns the list of all identity commitments, which are the leaves of 101 | * the Merkle tree. 102 | */ 103 | function getLeaves() public view returns (uint256[] memory) { 104 | return semaphore.leaves(semaphore.id_tree_index()); 105 | } 106 | 107 | 108 | /* 109 | * Inserts an identity commitment into Semaphore. 110 | * @param The identity commitment (the hash of the public key and the 111 | * identity nullifier) 112 | */ 113 | function insertIdentityCommitment(uint256 _identityCommitment) private { 114 | require(_identityCommitment != 0, "Mixer: invalid identity commitment"); 115 | semaphore.insertIdentity(_identityCommitment); 116 | } 117 | 118 | /* 119 | * Deposits `mixAmt` tokens into the contract and registers the user's 120 | * identity commitment into Semaphore. 121 | * @param The identity commitment (the hash of the public key and the 122 | * identity nullifier) 123 | */ 124 | 125 | function depositERC20(uint256 _identityCommitment) public onlyERC20 { 126 | // Transfer tokens from msg.sender to this contract 127 | bool transferSucceeded = token.transferFrom(msg.sender, address(this), mixAmt); 128 | 129 | // Ensure that the token transfer succeeded 130 | require(transferSucceeded, "Mixer: transferFrom() failed"); 131 | 132 | insertIdentityCommitment(_identityCommitment); 133 | 134 | emit DepositedERC20(msg.sender, mixAmt, _identityCommitment); 135 | } 136 | 137 | /* 138 | * Deposits `mixAmt` wei into the contract and registers the user's identity 139 | * commitment into Semaphore. 140 | * @param The identity commitment (the hash of the public key and the 141 | * identity nullifier) 142 | */ 143 | function deposit(uint256 _identityCommitment) public payable onlyEth { 144 | require(msg.value == mixAmt, "Mixer: wrong mixAmt deposited"); 145 | 146 | insertIdentityCommitment(_identityCommitment); 147 | 148 | emit Deposited(msg.sender, msg.value, _identityCommitment); 149 | } 150 | 151 | modifier validFee(uint256 fee) { 152 | // The fee must be high enough, but not larger than the mix 153 | // denomination; note that a self-interested relayer would exercise 154 | // their discretion as to whether to relay transactions depending on 155 | // the fee specified 156 | require(fee < mixAmt, "Mixer: quoted fee gte mixAmt"); 157 | _; 158 | } 159 | 160 | /* 161 | * Broadcasts the computed signal (the hash of the recipient's address, the 162 | * relayer's address, and the fee via Semaphore. 163 | */ 164 | function broadcastToSemaphore(DepositProof memory _proof, address payable _forwarderAddress) private { 165 | // Hash the recipient's address, the mixer contract's address, and fee 166 | bytes32 computedSignal = keccak256( 167 | abi.encodePacked( 168 | _proof.recipientAddress, 169 | _forwarderAddress, 170 | _proof.fee 171 | ) 172 | ); 173 | 174 | // Check whether the signal hash provided matches the one computed above 175 | require(computedSignal == _proof.signal, "Mixer: invalid computed signal"); 176 | 177 | // Broadcast the signal 178 | semaphore.broadcastSignal( 179 | abi.encode(_proof.signal), 180 | _proof.a, 181 | _proof.b, 182 | _proof.c, 183 | _proof.input 184 | ); 185 | } 186 | 187 | /* 188 | * Withdraw tokens to a specified recipient using a zk-SNARK deposit proof 189 | * @param _proof A deposit proof. This function will send `mixAmt` tokens, 190 | * minus fees, to the recipient if the proof is valid. 191 | * @param _forwarderAddress The address to send the fee to. 192 | */ 193 | function mixERC20(DepositProof memory _proof, address payable _forwarderAddress) public onlyERC20 validFee(_proof.fee) { 194 | broadcastToSemaphore(_proof, _forwarderAddress); 195 | 196 | // Transfer the fee to the relayer 197 | bool relayerTransferSucceeded = token.transfer(_forwarderAddress, _proof.fee); 198 | require(relayerTransferSucceeded, "Mixer: failed to transfer the fee in tokens to the relayer"); 199 | 200 | // Transfer the tokens owed to the recipient, minus the fee 201 | uint256 recipientMixAmt = mixAmt.sub(_proof.fee); 202 | bool recipientTransferSucceeded = token.transfer(_proof.recipientAddress, recipientMixAmt); 203 | require(recipientTransferSucceeded, "Mixer: failed to transfer mixAmt tokens to the recipient"); 204 | 205 | emit MixedERC20(_proof.recipientAddress, recipientMixAmt, _proof.fee); 206 | } 207 | 208 | /* 209 | * Withdraw funds to a specified recipient using a zk-SNARK deposit proof 210 | * @param _proof A deposit proof. This function will send `mixAmt` tokens, 211 | * minus the fee, to the recipient if the proof is valid. 212 | * @param _forwarderAddress The address to send the fee to. 213 | */ 214 | function mix(DepositProof memory _proof, address payable _forwarderAddress) public onlyEth validFee(_proof.fee) { 215 | broadcastToSemaphore(_proof, _forwarderAddress); 216 | 217 | // Transfer the fee to the relayer 218 | _forwarderAddress.transfer(_proof.fee); 219 | 220 | // Transfer the ETH owed to the recipient, minus the fee 221 | uint256 recipientMixAmt = mixAmt.sub(_proof.fee); 222 | _proof.recipientAddress.transfer(recipientMixAmt); 223 | 224 | emit Mixed(_proof.recipientAddress, recipientMixAmt, _proof.fee); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /contracts/solidity/MockRelayerRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | contract RelayerRegistry { 4 | function relayCall( 5 | address _applicationContract, 6 | bytes calldata _encodedPayload 7 | ) external payable { 8 | (bool success,) = _applicationContract.call(_encodedPayload); 9 | require(success, "RelayerForwarder: failure calling application contract"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /contracts/solidity/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * @dev Contract module which provides a basic access control mechanism, where 5 | * there is an account (an owner) that can be granted exclusive access to 6 | * specific functions. 7 | * 8 | * This module is used through inheritance. It will make available the modifier 9 | * `onlyOwner`, which can be aplied to your functions to restrict their use to 10 | * the owner. 11 | */ 12 | contract Ownable { 13 | address private _owner; 14 | 15 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 16 | 17 | /** 18 | * @dev Initializes the contract setting the deployer as the initial owner. 19 | */ 20 | constructor () internal { 21 | _owner = msg.sender; 22 | emit OwnershipTransferred(address(0), _owner); 23 | } 24 | 25 | /** 26 | * @dev Returns the address of the current owner. 27 | */ 28 | function owner() public view returns (address) { 29 | return _owner; 30 | } 31 | 32 | /** 33 | * @dev Throws if called by any account other than the owner. 34 | */ 35 | modifier onlyOwner() { 36 | require(isOwner(), "Ownable: caller is not the owner"); 37 | _; 38 | } 39 | 40 | /** 41 | * @dev Returns true if the caller is the current owner. 42 | */ 43 | function isOwner() public view returns (bool) { 44 | return msg.sender == _owner; 45 | } 46 | 47 | /** 48 | * @dev Leaves the contract without owner. It will not be possible to call 49 | * `onlyOwner` functions anymore. Can only be called by the current owner. 50 | * 51 | * > Note: Renouncing ownership will leave the contract without an owner, 52 | * thereby removing any functionality that is only available to the owner. 53 | */ 54 | function renounceOwnership() public onlyOwner { 55 | emit OwnershipTransferred(_owner, address(0)); 56 | _owner = address(0); 57 | } 58 | 59 | /** 60 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 61 | * Can only be called by the current owner. 62 | */ 63 | function transferOwnership(address newOwner) public onlyOwner { 64 | _transferOwnership(newOwner); 65 | } 66 | 67 | /** 68 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 69 | */ 70 | function _transferOwnership(address newOwner) internal { 71 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 72 | emit OwnershipTransferred(_owner, newOwner); 73 | _owner = newOwner; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /contracts/solidity/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * Copied from openzeppelin-contracts 2.3.0 5 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 6 | * checks. 7 | * 8 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 9 | * in bugs, because programmers usually assume that an overflow raises an 10 | * error, which is the standard behavior in high level programming languages. 11 | * `SafeMath` restores this intuition by reverting the transaction when an 12 | * operation overflows. 13 | * 14 | * Using this library instead of the unchecked operations eliminates an entire 15 | * class of bugs, so it's recommended to use it always. 16 | */ 17 | library SafeMath { 18 | /** 19 | * @dev Returns the addition of two unsigned integers, reverting on 20 | * overflow. 21 | * 22 | * Counterpart to Solidity's `+` operator. 23 | * 24 | * Requirements: 25 | * - Addition cannot overflow. 26 | */ 27 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 28 | uint256 c = a + b; 29 | require(c >= a, "SafeMath: addition overflow"); 30 | 31 | return c; 32 | } 33 | 34 | /** 35 | * @dev Returns the subtraction of two unsigned integers, reverting on 36 | * overflow (when the result is negative). 37 | * 38 | * Counterpart to Solidity's `-` operator. 39 | * 40 | * Requirements: 41 | * - Subtraction cannot overflow. 42 | */ 43 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 44 | require(b <= a, "SafeMath: subtraction overflow"); 45 | uint256 c = a - b; 46 | 47 | return c; 48 | } 49 | 50 | /** 51 | * @dev Returns the multiplication of two unsigned integers, reverting on 52 | * overflow. 53 | * 54 | * Counterpart to Solidity's `*` operator. 55 | * 56 | * Requirements: 57 | * - Multiplication cannot overflow. 58 | */ 59 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 60 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 61 | // benefit is lost if 'b' is also tested. 62 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 63 | if (a == 0) { 64 | return 0; 65 | } 66 | 67 | uint256 c = a * b; 68 | require(c / a == b, "SafeMath: multiplication overflow"); 69 | 70 | return c; 71 | } 72 | 73 | /** 74 | * @dev Returns the integer division of two unsigned integers. Reverts on 75 | * division by zero. The result is rounded towards zero. 76 | * 77 | * Counterpart to Solidity's `/` operator. Note: this function uses a 78 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 79 | * uses an invalid opcode to revert (consuming all remaining gas). 80 | * 81 | * Requirements: 82 | * - The divisor cannot be zero. 83 | */ 84 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 85 | // Solidity only automatically asserts when dividing by 0 86 | require(b > 0, "SafeMath: division by zero"); 87 | uint256 c = a / b; 88 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 89 | 90 | return c; 91 | } 92 | 93 | /** 94 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 95 | * Reverts when dividing by zero. 96 | * 97 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 98 | * opcode (which leaves remaining gas untouched) while Solidity uses an 99 | * invalid opcode to revert (consuming all remaining gas). 100 | * 101 | * Requirements: 102 | * - The divisor cannot be zero. 103 | */ 104 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 105 | require(b != 0, "SafeMath: modulo by zero"); 106 | return a % b; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /contracts/solidity/access/MinterRole.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./Roles.sol"; 4 | 5 | /* 6 | * Copied from openzeppelin-contracts 2.3.0 7 | */ 8 | contract MinterRole { 9 | using Roles for Roles.Role; 10 | 11 | event MinterAdded(address indexed account); 12 | event MinterRemoved(address indexed account); 13 | 14 | Roles.Role private _minters; 15 | 16 | constructor () internal { 17 | _addMinter(msg.sender); 18 | } 19 | 20 | modifier onlyMinter() { 21 | require(isMinter(msg.sender), "MinterRole: caller does not have the Minter role"); 22 | _; 23 | } 24 | 25 | function isMinter(address account) public view returns (bool) { 26 | return _minters.has(account); 27 | } 28 | 29 | function addMinter(address account) public onlyMinter { 30 | _addMinter(account); 31 | } 32 | 33 | function renounceMinter() public { 34 | _removeMinter(msg.sender); 35 | } 36 | 37 | function _addMinter(address account) internal { 38 | _minters.add(account); 39 | emit MinterAdded(account); 40 | } 41 | 42 | function _removeMinter(address account) internal { 43 | _minters.remove(account); 44 | emit MinterRemoved(account); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/solidity/access/Roles.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * Copied from openzeppelin-contracts 2.3.0 5 | * @title Roles 6 | * @dev Library for managing addresses assigned to a Role. 7 | */ 8 | library Roles { 9 | struct Role { 10 | mapping (address => bool) bearer; 11 | } 12 | 13 | /** 14 | * @dev Give an account access to this role. 15 | */ 16 | function add(Role storage role, address account) internal { 17 | require(!has(role, account), "Roles: account already has role"); 18 | role.bearer[account] = true; 19 | } 20 | 21 | /** 22 | * @dev Remove an account's access to this role. 23 | */ 24 | function remove(Role storage role, address account) internal { 25 | require(has(role, account), "Roles: account does not have role"); 26 | role.bearer[account] = false; 27 | } 28 | 29 | /** 30 | * @dev Check if an account has this role. 31 | * @return bool 32 | */ 33 | function has(Role storage role, address account) internal view returns (bool) { 34 | require(account != address(0), "Roles: account is the zero address"); 35 | return role.bearer[account]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/solidity/token/ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./IERC20.sol"; 4 | import "../SafeMath.sol"; 5 | 6 | /** 7 | * Copied from openzeppelin-contracts 2.3.0 8 | * @dev Implementation of the `IERC20` interface. 9 | * 10 | * This implementation is agnostic to the way tokens are created. This means 11 | * that a supply mechanism has to be added in a derived contract using `_mint`. 12 | * For a generic mechanism see `ERC20Mintable`. 13 | * 14 | * *For a detailed writeup see our guide [How to implement supply 15 | * mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).* 16 | * 17 | * We have followed general OpenZeppelin guidelines: functions revert instead 18 | * of returning `false` on failure. This behavior is nonetheless conventional 19 | * and does not conflict with the expectations of ERC20 applications. 20 | * 21 | * Additionally, an `Approval` event is emitted on calls to `transferFrom`. 22 | * This allows applications to reconstruct the allowance for all accounts just 23 | * by listening to said events. Other implementations of the EIP may not emit 24 | * these events, as it isn't required by the specification. 25 | * 26 | * Finally, the non-standard `decreaseAllowance` and `increaseAllowance` 27 | * functions have been added to mitigate the well-known issues around setting 28 | * allowances. See `IERC20.approve`. 29 | */ 30 | contract ERC20 is IERC20 { 31 | using SafeMath for uint256; 32 | 33 | mapping (address => uint256) private _balances; 34 | 35 | mapping (address => mapping (address => uint256)) private _allowances; 36 | 37 | uint256 private _totalSupply; 38 | 39 | /** 40 | * @dev See `IERC20.totalSupply`. 41 | */ 42 | function totalSupply() public view returns (uint256) { 43 | return _totalSupply; 44 | } 45 | 46 | /** 47 | * @dev See `IERC20.balanceOf`. 48 | */ 49 | function balanceOf(address account) public view returns (uint256) { 50 | return _balances[account]; 51 | } 52 | 53 | /** 54 | * @dev See `IERC20.transfer`. 55 | * 56 | * Requirements: 57 | * 58 | * - `recipient` cannot be the zero address. 59 | * - the caller must have a balance of at least `amount`. 60 | */ 61 | function transfer(address recipient, uint256 amount) public returns (bool) { 62 | _transfer(msg.sender, recipient, amount); 63 | return true; 64 | } 65 | 66 | /** 67 | * @dev See `IERC20.allowance`. 68 | */ 69 | function allowance(address owner, address spender) public view returns (uint256) { 70 | return _allowances[owner][spender]; 71 | } 72 | 73 | /** 74 | * @dev See `IERC20.approve`. 75 | * 76 | * Requirements: 77 | * 78 | * - `spender` cannot be the zero address. 79 | */ 80 | function approve(address spender, uint256 value) public returns (bool) { 81 | _approve(msg.sender, spender, value); 82 | return true; 83 | } 84 | 85 | /** 86 | * @dev See `IERC20.transferFrom`. 87 | * 88 | * Emits an `Approval` event indicating the updated allowance. This is not 89 | * required by the EIP. See the note at the beginning of `ERC20`; 90 | * 91 | * Requirements: 92 | * - `sender` and `recipient` cannot be the zero address. 93 | * - `sender` must have a balance of at least `value`. 94 | * - the caller must have allowance for `sender`'s tokens of at least 95 | * `amount`. 96 | */ 97 | function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) { 98 | _transfer(sender, recipient, amount); 99 | _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount)); 100 | return true; 101 | } 102 | 103 | /** 104 | * @dev Atomically increases the allowance granted to `spender` by the caller. 105 | * 106 | * This is an alternative to `approve` that can be used as a mitigation for 107 | * problems described in `IERC20.approve`. 108 | * 109 | * Emits an `Approval` event indicating the updated allowance. 110 | * 111 | * Requirements: 112 | * 113 | * - `spender` cannot be the zero address. 114 | */ 115 | function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { 116 | _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); 117 | return true; 118 | } 119 | 120 | /** 121 | * @dev Atomically decreases the allowance granted to `spender` by the caller. 122 | * 123 | * This is an alternative to `approve` that can be used as a mitigation for 124 | * problems described in `IERC20.approve`. 125 | * 126 | * Emits an `Approval` event indicating the updated allowance. 127 | * 128 | * Requirements: 129 | * 130 | * - `spender` cannot be the zero address. 131 | * - `spender` must have allowance for the caller of at least 132 | * `subtractedValue`. 133 | */ 134 | function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { 135 | _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue)); 136 | return true; 137 | } 138 | 139 | /** 140 | * @dev Moves tokens `amount` from `sender` to `recipient`. 141 | * 142 | * This is internal function is equivalent to `transfer`, and can be used to 143 | * e.g. implement automatic token fees, slashing mechanisms, etc. 144 | * 145 | * Emits a `Transfer` event. 146 | * 147 | * Requirements: 148 | * 149 | * - `sender` cannot be the zero address. 150 | * - `recipient` cannot be the zero address. 151 | * - `sender` must have a balance of at least `amount`. 152 | */ 153 | function _transfer(address sender, address recipient, uint256 amount) internal { 154 | require(sender != address(0), "ERC20: transfer from the zero address"); 155 | require(recipient != address(0), "ERC20: transfer to the zero address"); 156 | 157 | _balances[sender] = _balances[sender].sub(amount); 158 | _balances[recipient] = _balances[recipient].add(amount); 159 | emit Transfer(sender, recipient, amount); 160 | } 161 | 162 | /** @dev Creates `amount` tokens and assigns them to `account`, increasing 163 | * the total supply. 164 | * 165 | * Emits a `Transfer` event with `from` set to the zero address. 166 | * 167 | * Requirements 168 | * 169 | * - `to` cannot be the zero address. 170 | */ 171 | function _mint(address account, uint256 amount) internal { 172 | require(account != address(0), "ERC20: mint to the zero address"); 173 | 174 | _totalSupply = _totalSupply.add(amount); 175 | _balances[account] = _balances[account].add(amount); 176 | emit Transfer(address(0), account, amount); 177 | } 178 | 179 | /** 180 | * @dev Destoys `amount` tokens from `account`, reducing the 181 | * total supply. 182 | * 183 | * Emits a `Transfer` event with `to` set to the zero address. 184 | * 185 | * Requirements 186 | * 187 | * - `account` cannot be the zero address. 188 | * - `account` must have at least `amount` tokens. 189 | */ 190 | function _burn(address account, uint256 value) internal { 191 | require(account != address(0), "ERC20: burn from the zero address"); 192 | 193 | _totalSupply = _totalSupply.sub(value); 194 | _balances[account] = _balances[account].sub(value); 195 | emit Transfer(account, address(0), value); 196 | } 197 | 198 | /** 199 | * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. 200 | * 201 | * This is internal function is equivalent to `approve`, and can be used to 202 | * e.g. set automatic allowances for certain subsystems, etc. 203 | * 204 | * Emits an `Approval` event. 205 | * 206 | * Requirements: 207 | * 208 | * - `owner` cannot be the zero address. 209 | * - `spender` cannot be the zero address. 210 | */ 211 | function _approve(address owner, address spender, uint256 value) internal { 212 | require(owner != address(0), "ERC20: approve from the zero address"); 213 | require(spender != address(0), "ERC20: approve to the zero address"); 214 | 215 | _allowances[owner][spender] = value; 216 | emit Approval(owner, spender, value); 217 | } 218 | 219 | /** 220 | * @dev Destoys `amount` tokens from `account`.`amount` is then deducted 221 | * from the caller's allowance. 222 | * 223 | * See `_burn` and `_approve`. 224 | */ 225 | function _burnFrom(address account, uint256 amount) internal { 226 | _burn(account, amount); 227 | _approve(account, msg.sender, _allowances[account][msg.sender].sub(amount)); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /contracts/solidity/token/ERC20Detailed.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./IERC20.sol"; 4 | 5 | /** 6 | * @dev Optional functions from the ERC20 standard. 7 | */ 8 | contract ERC20Detailed is IERC20 { 9 | string private _name; 10 | string private _symbol; 11 | uint8 private _decimals; 12 | 13 | /** 14 | * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of 15 | * these values are immutable: they can only be set once during 16 | * construction. 17 | */ 18 | constructor (string memory name, string memory symbol, uint8 decimals) public { 19 | _name = name; 20 | _symbol = symbol; 21 | _decimals = decimals; 22 | } 23 | 24 | /** 25 | * @dev Returns the name of the token. 26 | */ 27 | function name() public view returns (string memory) { 28 | return _name; 29 | } 30 | 31 | /** 32 | * @dev Returns the symbol of the token, usually a shorter version of the 33 | * name. 34 | */ 35 | function symbol() public view returns (string memory) { 36 | return _symbol; 37 | } 38 | 39 | /** 40 | * @dev Returns the number of decimals used to get its user representation. 41 | * For example, if `decimals` equals `2`, a balance of `505` tokens should 42 | * be displayed to a user as `5,05` (`505 / 10 ** 2`). 43 | * 44 | * Tokens usually opt for a value of 18, imitating the relationship between 45 | * Ether and Wei. 46 | * 47 | * > Note that this information is only used for _display_ purposes: it in 48 | * no way affects any of the arithmetic of the contract, including 49 | * `IERC20.balanceOf` and `IERC20.transfer`. 50 | */ 51 | function decimals() public view returns (uint8) { 52 | return _decimals; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /contracts/solidity/token/ERC20Mintable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | import "./ERC20.sol"; 4 | import "./ERC20Detailed.sol"; 5 | import "../access/MinterRole.sol"; 6 | 7 | /** 8 | * Copied from openzeppelin-contracts 2.3.0 9 | * @dev Extension of `ERC20` that adds a set of accounts with the `MinterRole`, 10 | * which have permission to mint (create) new tokens as they see fit. 11 | * 12 | * At construction, the deployer of the contract is the only minter. 13 | */ 14 | contract ERC20Mintable is ERC20, ERC20Detailed, MinterRole { 15 | 16 | constructor (string memory name, string memory symbol, uint8 decimals) 17 | public 18 | ERC20Detailed(name, symbol, decimals) 19 | { } 20 | 21 | /** 22 | * @dev See `ERC20._mint`. 23 | * 24 | * Requirements: 25 | * 26 | * - the caller must have the `MinterRole`. 27 | */ 28 | function mint(address account, uint256 amount) public onlyMinter returns (bool) { 29 | _mint(account, amount); 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/solidity/token/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /** 4 | * Copied from openzeppelin-contracts 2.3.0 5 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 6 | * the optional functions; to access them see `ERC20Detailed`. 7 | */ 8 | interface IERC20 { 9 | /** 10 | * @dev Returns the amount of tokens in existence. 11 | */ 12 | function totalSupply() external view returns (uint256); 13 | 14 | /** 15 | * @dev Returns the amount of tokens owned by `account`. 16 | */ 17 | function balanceOf(address account) external view returns (uint256); 18 | 19 | /** 20 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 21 | * 22 | * Returns a boolean value indicating whether the operation succeeded. 23 | * 24 | * Emits a `Transfer` event. 25 | */ 26 | function transfer(address recipient, uint256 amount) external returns (bool); 27 | 28 | /** 29 | * @dev Returns the remaining number of tokens that `spender` will be 30 | * allowed to spend on behalf of `owner` through `transferFrom`. This is 31 | * zero by default. 32 | * 33 | * This value changes when `approve` or `transferFrom` are called. 34 | */ 35 | function allowance(address owner, address spender) external view returns (uint256); 36 | 37 | /** 38 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 39 | * 40 | * Returns a boolean value indicating whether the operation succeeded. 41 | * 42 | * > Beware that changing an allowance with this method brings the risk 43 | * that someone may use both the old and the new allowance by unfortunate 44 | * transaction ordering. One possible solution to mitigate this race 45 | * condition is to first reduce the spender's allowance to 0 and set the 46 | * desired value afterwards: 47 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 48 | * 49 | * Emits an `Approval` event. 50 | */ 51 | function approve(address spender, uint256 amount) external returns (bool); 52 | 53 | /** 54 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 55 | * allowance mechanism. `amount` is then deducted from the caller's 56 | * allowance. 57 | * 58 | * Returns a boolean value indicating whether the operation succeeded. 59 | * 60 | * Emits a `Transfer` event. 61 | */ 62 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 63 | 64 | /** 65 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 66 | * another (`to`). 67 | * 68 | * Note that `value` may be zero. 69 | */ 70 | event Transfer(address indexed from, address indexed to, uint256 value); 71 | 72 | /** 73 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 74 | * a call to `approve`. `value` is the new allowance. 75 | */ 76 | event Approval(address indexed owner, address indexed spender, uint256 value); 77 | } 78 | -------------------------------------------------------------------------------- /contracts/ts/__tests__/index.d.ts: -------------------------------------------------------------------------------- 1 | declare var accounts: any[] 2 | declare var utils: any 3 | //declare var assert: any 4 | //declare var before: any 5 | //declare var it: any 6 | //declare var describe: any 7 | -------------------------------------------------------------------------------- /contracts/ts/__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | import * as ethers from 'ethers' 2 | import { 3 | SnarkProvingKey, 4 | SnarkVerifyingKey, 5 | genCircuit, 6 | parseVerifyingKeyJson, 7 | } from 'libsemaphore' 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | 11 | const mix = async ( 12 | relayerRegistryContract, 13 | mixerContract, 14 | signal, 15 | proof, 16 | publicSignals, 17 | recipientAddress, 18 | feeAmt, 19 | relayerAddress, 20 | ) => { 21 | const depositProof = genDepositProof( 22 | signal, 23 | proof, 24 | publicSignals, 25 | recipientAddress, 26 | feeAmt, 27 | ) 28 | const iface = new ethers.utils.Interface(mixerContract.interface.abi) 29 | const callData = iface.functions.mix.encode([depositProof, relayerAddress]) 30 | 31 | return relayerRegistryContract.relayCall( 32 | mixerContract.contractAddress, 33 | callData, 34 | { gasLimit: 1000000 } 35 | ) 36 | 37 | //return await mixerContract.mix( 38 | //depositProof, 39 | //relayerAddress, 40 | //{ gasLimit: 1000000 } 41 | //) 42 | } 43 | 44 | const mixERC20 = async ( 45 | relayerRegistryContract, 46 | mixerContract, 47 | signal, 48 | proof, 49 | publicSignals, 50 | recipientAddress, 51 | feeAmt, 52 | relayerAddress, 53 | ) => { 54 | const depositProof = genDepositProof( 55 | signal, 56 | proof, 57 | publicSignals, 58 | recipientAddress, 59 | feeAmt, 60 | ) 61 | const iface = new ethers.utils.Interface(mixerContract.interface.abi) 62 | const callData = iface.functions.mixERC20.encode([depositProof, relayerAddress]) 63 | 64 | return relayerRegistryContract.relayCall( 65 | mixerContract.contractAddress, 66 | callData, 67 | { gasLimit: 1000000 }, 68 | ) 69 | } 70 | 71 | const genDepositProof = ( 72 | signal, 73 | proof, 74 | publicSignals, 75 | recipientAddress, 76 | feeAmt, 77 | ) => { 78 | return { 79 | signal, 80 | a: [ proof.pi_a[0].toString(), proof.pi_a[1].toString() ], 81 | b: [ 82 | [ proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString() ], 83 | [ proof.pi_b[1][1].toString(), proof.pi_b[1][0].toString() ], 84 | ], 85 | c: [ proof.pi_c[0].toString(), proof.pi_c[1].toString() ], 86 | input: [ 87 | publicSignals[0].toString(), 88 | publicSignals[1].toString(), 89 | publicSignals[2].toString(), 90 | publicSignals[3].toString(), 91 | ], 92 | recipientAddress, 93 | fee: feeAmt, 94 | } 95 | } 96 | 97 | const areEqualAddresses = (a: string, b: string) => { 98 | return BigInt(a) === BigInt(b) 99 | } 100 | 101 | const getSnarks = () => { 102 | const verifyingKey = parseVerifyingKeyJson(fs.readFileSync( 103 | path.join( 104 | __dirname, 105 | '../../../semaphore/semaphorejs/build/verification_key.json', 106 | ) 107 | )) 108 | 109 | const provingKey: SnarkProvingKey = fs.readFileSync( 110 | path.join(__dirname, '../../../semaphore/semaphorejs/build/proving_key.bin'), 111 | ) 112 | const circuitPath = '../../../semaphore/semaphorejs/build/circuit.json' 113 | const cirDef = JSON.parse( 114 | fs.readFileSync(path.join(__dirname, circuitPath)).toString() 115 | ) 116 | 117 | const circuit = genCircuit(cirDef) 118 | 119 | return { 120 | verifyingKey, 121 | provingKey, 122 | circuit, 123 | } 124 | } 125 | 126 | export { 127 | genDepositProof, 128 | areEqualAddresses, 129 | mix, 130 | mixERC20, 131 | getSnarks, 132 | } 133 | -------------------------------------------------------------------------------- /contracts/ts/accounts.ts: -------------------------------------------------------------------------------- 1 | import * as ethers from 'ethers' 2 | import { config } from 'mixer-config' 3 | 4 | const privateKeys = require(config.get('chain.privateKeysPath')) 5 | 6 | const genAccounts = () => { 7 | return privateKeys.map((pk: string) => { 8 | return new ethers.Wallet(pk) 9 | }) 10 | } 11 | 12 | const genTestAccounts = (num: number, mnemonic: string) => { 13 | let accounts: ethers.Wallet[] = [] 14 | 15 | for (let i=0; i { 7 | await artifactor.save({ 8 | contractName: 'MiMC', 9 | abi: mimcGenContract.abi, 10 | unlinked_binary: mimcGenContract.createCode(SEED, 220), 11 | }) 12 | } 13 | 14 | if (require.main === module) { 15 | buildMiMC() 16 | } 17 | 18 | export default buildMiMC 19 | -------------------------------------------------------------------------------- /contracts/ts/deploy/deploy.ts: -------------------------------------------------------------------------------- 1 | require('module-alias/register') 2 | import * as ethers from 'ethers' 3 | import * as argparse from 'argparse' 4 | import * as fs from 'fs' 5 | import * as path from 'path' 6 | import * as etherlime from 'etherlime-lib' 7 | import { config } from 'mixer-config' 8 | import { genAccounts } from '../accounts' 9 | 10 | const ERC20Mintable = require('@mixer-contracts/compiled/ERC20Mintable.json') 11 | 12 | const deploySemaphore = (deployer, Semaphore, libraries) => { 13 | return deployer.deploy( 14 | Semaphore, 15 | libraries, 16 | 20, 17 | 0, 18 | 12312, 19 | 1000, 20 | ) 21 | } 22 | 23 | const _deployMixer = ( 24 | deployer, 25 | Mixer, 26 | semaphoreContractAddress, 27 | mixAmtTokens, 28 | tokenAddress, 29 | ) => { 30 | 31 | return deployer.deploy(Mixer, 32 | {}, 33 | semaphoreContractAddress, 34 | mixAmtTokens.toString(), 35 | tokenAddress, 36 | ) 37 | } 38 | 39 | const deployTokenMixer = _deployMixer 40 | 41 | const deployEthMixer = ( 42 | deployer, 43 | Mixer, 44 | semaphoreContractAddress, 45 | mixAmtEth, 46 | ) => { 47 | 48 | return _deployMixer( 49 | deployer, 50 | Mixer, 51 | semaphoreContractAddress, 52 | mixAmtEth, 53 | '0x0000000000000000000000000000000000000000', 54 | ) 55 | } 56 | 57 | const deployToken = async ( 58 | deployer: any, 59 | ) => { 60 | const tokenContract = await deployer.deploy( 61 | ERC20Mintable, 62 | {}, 63 | 'Token', 64 | 'TKN', 65 | 18, 66 | ) 67 | 68 | return tokenContract 69 | } 70 | 71 | const deployAllContracts = async ( 72 | deployer, 73 | mixAmtEth, 74 | mixAmtTokens, 75 | adminAddress, 76 | ) => { 77 | // Deploy token if it's not specified in config. This should be the case for local-dev.yaml 78 | // In Kovan, the DAI address is 0xc4375b7de8af5a38a93548eb8453a498222c4ff2 79 | let tokenAddress = config.chain.deployedAddresses.Token 80 | let tokenContract 81 | let tokenDecimals = config.get('tokenDecimals') 82 | 83 | if (config.env !== 'local-dev') { 84 | console.log('Using existing token contract at', tokenAddress) 85 | tokenContract = new ethers.Contract( 86 | tokenAddress, 87 | ERC20Mintable.abi, 88 | deployer.signer, 89 | ) 90 | } else { 91 | console.log('Deploying token') 92 | tokenContract = await deployToken(deployer) 93 | tokenAddress = tokenContract.address 94 | } 95 | 96 | tokenAddress = tokenContract.contractAddress ? tokenContract.contractAddress : tokenContract.address 97 | 98 | const MiMC = require('@mixer-contracts/compiled/MiMC.json') 99 | const Semaphore = require('@mixer-contracts/compiled/Semaphore.json') 100 | const Mixer = require('@mixer-contracts/compiled/Mixer.json') 101 | const RelayerRegistry = require('@mixer-contracts/compiled/RelayerRegistry.json') 102 | 103 | console.log('Deploying MiMC') 104 | const mimcContract = await deployer.deploy(MiMC, {}) 105 | 106 | const libraries = { 107 | MiMC: mimcContract.contractAddress, 108 | } 109 | 110 | console.log('Deploying Semaphore') 111 | const semaphoreContract = await deploySemaphore( 112 | deployer, 113 | Semaphore, 114 | libraries, 115 | ) 116 | 117 | console.log('Deploying the ETH Mixer') 118 | const mixerContract = await deployEthMixer( 119 | deployer, 120 | Mixer, 121 | semaphoreContract.contractAddress, 122 | mixAmtEth, 123 | ) 124 | 125 | console.log('Transferring ownership of Semaphore to the ETH Mixer') 126 | let tx = await semaphoreContract.transferOwnership(mixerContract.contractAddress) 127 | await tx.wait() 128 | 129 | console.log('Setting the external nullifier of the Semaphore contract') 130 | tx = await mixerContract.setSemaphoreExternalNulllifier({ gasLimit: 100000 }) 131 | await tx.wait() 132 | 133 | console.log('Deploying Semaphore for the Token Mixer') 134 | const tokenSemaphoreContract = await deploySemaphore( 135 | deployer, 136 | Semaphore, 137 | libraries, 138 | ) 139 | 140 | console.log('Deploying the Token Mixer') 141 | const tokenMixerContract = await deployTokenMixer( 142 | deployer, 143 | Mixer, 144 | tokenSemaphoreContract.contractAddress, 145 | mixAmtTokens * (10 ** tokenDecimals), 146 | tokenAddress, 147 | ) 148 | 149 | console.log('Transferring ownership of Token Semaphore to the Token Mixer') 150 | tx = await tokenSemaphoreContract.transferOwnership(tokenMixerContract.contractAddress) 151 | await tx.wait() 152 | 153 | console.log('Setting the external nullifier of the Token Semaphore contract') 154 | tx = await tokenMixerContract.setSemaphoreExternalNulllifier({ gasLimit: 100000 }) 155 | await tx.wait() 156 | 157 | console.log('Deploying Relayer Registry') 158 | const relayerRegistryContract = await deployer.deploy(RelayerRegistry, {}) 159 | 160 | if (config.env === 'local-dev') { 161 | console.log('Minting tokens') 162 | await tokenContract.mint(adminAddress, '100000000000000000000000000') 163 | } 164 | 165 | return { 166 | mimcContract, 167 | semaphoreContract, 168 | mixerContract, 169 | relayerRegistryContract, 170 | tokenSemaphoreContract, 171 | tokenMixerContract, 172 | tokenContract, 173 | } 174 | } 175 | 176 | const main = async () => { 177 | const accounts = genAccounts() 178 | const admin = accounts[0] 179 | 180 | console.log('Using account', admin.address) 181 | 182 | const parser = new argparse.ArgumentParser({ 183 | description: 'Deploy all contracts to an Ethereum network of your choice' 184 | }) 185 | 186 | parser.addArgument( 187 | ['-o', '--output'], 188 | { 189 | help: 'The filepath to save the addresses of the deployed contracts', 190 | required: true 191 | } 192 | ) 193 | 194 | const args = parser.parseArgs() 195 | const outputAddressFile = args.output 196 | 197 | const deployer = new etherlime.JSONRPCPrivateKeyDeployer( 198 | admin.privateKey, 199 | config.get('chain.url'), 200 | { 201 | chainId: config.get('chain.chainId'), 202 | }, 203 | ) 204 | 205 | const { 206 | mimcContract, 207 | semaphoreContract, 208 | mixerContract, 209 | relayerRegistryContract, 210 | tokenContract, 211 | tokenSemaphoreContract, 212 | tokenMixerContract, 213 | } = await deployAllContracts( 214 | deployer, 215 | ethers.utils.parseEther(config.mixAmtEth.toString()), 216 | config.mixAmtTokens, 217 | admin.address, 218 | ) 219 | 220 | const addresses = { 221 | MiMC: mimcContract.contractAddress, 222 | Semaphore: semaphoreContract.contractAddress, 223 | Mixer: mixerContract.contractAddress, 224 | TokenMixer: tokenMixerContract.contractAddress, 225 | TokenSemaphore: tokenSemaphoreContract.contractAddress, 226 | RelayerRegistry: relayerRegistryContract.contractAddress, 227 | Token: tokenContract.contractAddress ? tokenContract.contractAddress : tokenContract.address, 228 | } 229 | 230 | const addressJsonPath = path.join(__dirname, '../..', outputAddressFile) 231 | fs.writeFileSync( 232 | addressJsonPath, 233 | JSON.stringify(addresses), 234 | ) 235 | 236 | console.log(addresses) 237 | } 238 | 239 | if (require.main === module) { 240 | try { 241 | main() 242 | } catch (err) { 243 | console.error(err) 244 | } 245 | } 246 | 247 | export { 248 | deployToken, 249 | deployAllContracts, 250 | } 251 | -------------------------------------------------------------------------------- /contracts/ts/index.ts: -------------------------------------------------------------------------------- 1 | import * as ethers from 'ethers' 2 | 3 | const getContract = ( 4 | name: string, 5 | signer: ethers.Signer, 6 | deployedAddresses: object, 7 | abiName?: string 8 | ) => { 9 | if (!abiName) { 10 | abiName = name 11 | } 12 | 13 | const abi = require(`../compiled/abis/${abiName}-abi.json`) 14 | 15 | const contract = new ethers.Contract( 16 | deployedAddresses[name], 17 | abi, 18 | signer, 19 | ) 20 | 21 | return contract 22 | } 23 | 24 | export { getContract } 25 | -------------------------------------------------------------------------------- /contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./build", 5 | "resolveJsonModule": true 6 | }, 7 | "include": [ 8 | "./ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | mixer-backend: 4 | container_name: mixer-backend 5 | 6 | build: 7 | context: ../backend 8 | 9 | ports: 10 | - "3000:3000" 11 | 12 | networks: 13 | - "mixer-net" 14 | 15 | environment: 16 | - NODE_ENV=docker 17 | 18 | secrets: 19 | - hotWalletPrivKeyPath 20 | 21 | mixer-frontend: 22 | container_name: mixer-frontend 23 | 24 | build: 25 | context: ../frontend 26 | 27 | ports: 28 | - "80:8001" 29 | 30 | networks: 31 | - "mixer-net" 32 | 33 | environment: 34 | - NODE_ENV=docker 35 | 36 | mixer-etcd: 37 | container_name: mixer-etcd 38 | 39 | image: jumanjiman/etcd 40 | 41 | ports: 42 | - "2379:2000" 43 | 44 | networks: 45 | - "mixer-net" 46 | 47 | environment: 48 | - NODE_ENV=docker 49 | 50 | networks: 51 | mixer-net: 52 | 53 | secrets: 54 | privateKeysPath: 55 | file: "../../MIXER_SECRETS/privateKeys.json" 56 | 57 | hotWalletPrivKeyPath: 58 | file: "../../MIXER_SECRETS/hotWalletPrivKey.json" 59 | -------------------------------------------------------------------------------- /docs/img/dev_screens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/docs/img/dev_screens.png -------------------------------------------------------------------------------- /docs/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/docs/img/logo.png -------------------------------------------------------------------------------- /frontend/.terserrc: -------------------------------------------------------------------------------- 1 | { 2 | "mangle": false, 3 | "compress": true 4 | } 5 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.17.1-alpine AS mixer-frontend 2 | 3 | COPY --from=mixer-base /mixer/frontend/dist /static 4 | COPY --from=mixer-base /mixer/frontend/nginx.conf /etc/nginx/nginx.conf 5 | 6 | WORKDIR / 7 | 8 | CMD nginx -c /etc/nginx/nginx.conf -g 'daemon off;' 9 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Mixer Frontend 2 | 3 | The frontend is a React 16 app written in TypeScript. It uses Parcel to handle 4 | source bundling. 5 | 6 | ## Instructions 7 | 8 | First, get at least 0.102 Kovan ETH (0.1 plus gas fees) from a 9 | [faucet](https://faucet.kovan.network/). Next, navigate to the UI in your web 10 | browser. 11 | 12 | Enter the recipient's ETH address and click "Mix 0.1 ETH". This will trigger a 13 | MetaMask popup. Click submit. Do not close your browser window until you see 14 | the Countdown page. 15 | 16 | Keep this browser window open till midnight UTC for the page to automatically 17 | mix the funds. To speed up this process for testing purposes, you can set the 18 | timestamp of the last entry in the localStorage `MIXER` JSON array to `0`, and 19 | a yellow button will appear which you can click to trigger the mix. 20 | 21 | This mix process downloads about 40MB worth of gzipped zk-SNARK keys and 22 | circuit data, generates a proof, and submits it to a centralised but 23 | noncustodial relayer. The relayer verifies the proof and submits a `mix()` 24 | transaction to the mixer contract located 25 | [here](https://kovan.etherscan.io/address/0xfb2bf70382a98c72d38bed63735ff5115ff243c6). 26 | 27 | ## Development 28 | 29 | For a hot-reloading development setup, run the following command in this 30 | directory: 31 | 32 | ```bash 33 | npm run watch 34 | ``` 35 | 36 | And launch [http://localhost:1234](http://localhost:1234) in your browser. 37 | 38 | ## Production 39 | 40 | To create a production build, run: 41 | 42 | ```bash 43 | npm run build 44 | ``` 45 | 46 | ## Stylesheets 47 | 48 | All stylesheets are written in LESS and stored in `less/`. 49 | 50 | `index.html` imports `index.less`. In turn, `index.less` imports all other 51 | stylesheets named `index.less` in various subfolders. 52 | 53 | We use convention to separate of style concerns. 54 | 55 | - `constants.less`: defines colour values, lengths, font sizes, and any 56 | absolute values used by the other stylesheets. 57 | 58 | - `routes/index.less`: defines styles common to all routes, and also imports 59 | all stylesheets in `routes/`. 60 | 61 | - `routes/.less`: defines styles specific to `routes/.tsx` 62 | 63 | - `components/index.less`: defines styles common to all components, and also imports 64 | all stylesheets in `components/`. 65 | 66 | - `components/.less`: defines styles specific to `components/.tsx` 67 | 68 | The layout should be responsive. Media queries should go into each individual 69 | stylesheet. e.g. `constants.less` should contain a media query which sets 70 | different margins for different screen sizes, `deposit.less` should handle its 71 | own responsive styles, etc. 72 | -------------------------------------------------------------------------------- /frontend/abis/ERC20-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "from", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "to", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "value", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "Transfer", 25 | "type": "event" 26 | }, 27 | { 28 | "anonymous": false, 29 | "inputs": [ 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "owner", 34 | "type": "address" 35 | }, 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "spender", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "value", 46 | "type": "uint256" 47 | } 48 | ], 49 | "name": "Approval", 50 | "type": "event" 51 | }, 52 | { 53 | "constant": true, 54 | "inputs": [], 55 | "name": "totalSupply", 56 | "outputs": [ 57 | { 58 | "internalType": "uint256", 59 | "name": "", 60 | "type": "uint256" 61 | } 62 | ], 63 | "payable": false, 64 | "stateMutability": "view", 65 | "type": "function" 66 | }, 67 | { 68 | "constant": true, 69 | "inputs": [ 70 | { 71 | "internalType": "address", 72 | "name": "account", 73 | "type": "address" 74 | } 75 | ], 76 | "name": "balanceOf", 77 | "outputs": [ 78 | { 79 | "internalType": "uint256", 80 | "name": "", 81 | "type": "uint256" 82 | } 83 | ], 84 | "payable": false, 85 | "stateMutability": "view", 86 | "type": "function" 87 | }, 88 | { 89 | "constant": false, 90 | "inputs": [ 91 | { 92 | "internalType": "address", 93 | "name": "recipient", 94 | "type": "address" 95 | }, 96 | { 97 | "internalType": "uint256", 98 | "name": "amount", 99 | "type": "uint256" 100 | } 101 | ], 102 | "name": "transfer", 103 | "outputs": [ 104 | { 105 | "internalType": "bool", 106 | "name": "", 107 | "type": "bool" 108 | } 109 | ], 110 | "payable": false, 111 | "stateMutability": "nonpayable", 112 | "type": "function" 113 | }, 114 | { 115 | "constant": true, 116 | "inputs": [ 117 | { 118 | "internalType": "address", 119 | "name": "owner", 120 | "type": "address" 121 | }, 122 | { 123 | "internalType": "address", 124 | "name": "spender", 125 | "type": "address" 126 | } 127 | ], 128 | "name": "allowance", 129 | "outputs": [ 130 | { 131 | "internalType": "uint256", 132 | "name": "", 133 | "type": "uint256" 134 | } 135 | ], 136 | "payable": false, 137 | "stateMutability": "view", 138 | "type": "function" 139 | }, 140 | { 141 | "constant": false, 142 | "inputs": [ 143 | { 144 | "internalType": "address", 145 | "name": "spender", 146 | "type": "address" 147 | }, 148 | { 149 | "internalType": "uint256", 150 | "name": "value", 151 | "type": "uint256" 152 | } 153 | ], 154 | "name": "approve", 155 | "outputs": [ 156 | { 157 | "internalType": "bool", 158 | "name": "", 159 | "type": "bool" 160 | } 161 | ], 162 | "payable": false, 163 | "stateMutability": "nonpayable", 164 | "type": "function" 165 | }, 166 | { 167 | "constant": false, 168 | "inputs": [ 169 | { 170 | "internalType": "address", 171 | "name": "sender", 172 | "type": "address" 173 | }, 174 | { 175 | "internalType": "address", 176 | "name": "recipient", 177 | "type": "address" 178 | }, 179 | { 180 | "internalType": "uint256", 181 | "name": "amount", 182 | "type": "uint256" 183 | } 184 | ], 185 | "name": "transferFrom", 186 | "outputs": [ 187 | { 188 | "internalType": "bool", 189 | "name": "", 190 | "type": "bool" 191 | } 192 | ], 193 | "payable": false, 194 | "stateMutability": "nonpayable", 195 | "type": "function" 196 | }, 197 | { 198 | "constant": false, 199 | "inputs": [ 200 | { 201 | "internalType": "address", 202 | "name": "spender", 203 | "type": "address" 204 | }, 205 | { 206 | "internalType": "uint256", 207 | "name": "addedValue", 208 | "type": "uint256" 209 | } 210 | ], 211 | "name": "increaseAllowance", 212 | "outputs": [ 213 | { 214 | "internalType": "bool", 215 | "name": "", 216 | "type": "bool" 217 | } 218 | ], 219 | "payable": false, 220 | "stateMutability": "nonpayable", 221 | "type": "function" 222 | }, 223 | { 224 | "constant": false, 225 | "inputs": [ 226 | { 227 | "internalType": "address", 228 | "name": "spender", 229 | "type": "address" 230 | }, 231 | { 232 | "internalType": "uint256", 233 | "name": "subtractedValue", 234 | "type": "uint256" 235 | } 236 | ], 237 | "name": "decreaseAllowance", 238 | "outputs": [ 239 | { 240 | "internalType": "bool", 241 | "name": "", 242 | "type": "bool" 243 | } 244 | ], 245 | "payable": false, 246 | "stateMutability": "nonpayable", 247 | "type": "function" 248 | } 249 | ] -------------------------------------------------------------------------------- /frontend/abis/ERC20Detailed-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": false, 4 | "inputs": [ 5 | { 6 | "internalType": "address", 7 | "name": "spender", 8 | "type": "address" 9 | }, 10 | { 11 | "internalType": "uint256", 12 | "name": "amount", 13 | "type": "uint256" 14 | } 15 | ], 16 | "name": "approve", 17 | "outputs": [ 18 | { 19 | "internalType": "bool", 20 | "name": "", 21 | "type": "bool" 22 | } 23 | ], 24 | "payable": false, 25 | "stateMutability": "nonpayable", 26 | "type": "function" 27 | }, 28 | { 29 | "constant": true, 30 | "inputs": [], 31 | "name": "totalSupply", 32 | "outputs": [ 33 | { 34 | "internalType": "uint256", 35 | "name": "", 36 | "type": "uint256" 37 | } 38 | ], 39 | "payable": false, 40 | "stateMutability": "view", 41 | "type": "function" 42 | }, 43 | { 44 | "constant": false, 45 | "inputs": [ 46 | { 47 | "internalType": "address", 48 | "name": "sender", 49 | "type": "address" 50 | }, 51 | { 52 | "internalType": "address", 53 | "name": "recipient", 54 | "type": "address" 55 | }, 56 | { 57 | "internalType": "uint256", 58 | "name": "amount", 59 | "type": "uint256" 60 | } 61 | ], 62 | "name": "transferFrom", 63 | "outputs": [ 64 | { 65 | "internalType": "bool", 66 | "name": "", 67 | "type": "bool" 68 | } 69 | ], 70 | "payable": false, 71 | "stateMutability": "nonpayable", 72 | "type": "function" 73 | }, 74 | { 75 | "constant": true, 76 | "inputs": [ 77 | { 78 | "internalType": "address", 79 | "name": "account", 80 | "type": "address" 81 | } 82 | ], 83 | "name": "balanceOf", 84 | "outputs": [ 85 | { 86 | "internalType": "uint256", 87 | "name": "", 88 | "type": "uint256" 89 | } 90 | ], 91 | "payable": false, 92 | "stateMutability": "view", 93 | "type": "function" 94 | }, 95 | { 96 | "constant": false, 97 | "inputs": [ 98 | { 99 | "internalType": "address", 100 | "name": "recipient", 101 | "type": "address" 102 | }, 103 | { 104 | "internalType": "uint256", 105 | "name": "amount", 106 | "type": "uint256" 107 | } 108 | ], 109 | "name": "transfer", 110 | "outputs": [ 111 | { 112 | "internalType": "bool", 113 | "name": "", 114 | "type": "bool" 115 | } 116 | ], 117 | "payable": false, 118 | "stateMutability": "nonpayable", 119 | "type": "function" 120 | }, 121 | { 122 | "constant": true, 123 | "inputs": [ 124 | { 125 | "internalType": "address", 126 | "name": "owner", 127 | "type": "address" 128 | }, 129 | { 130 | "internalType": "address", 131 | "name": "spender", 132 | "type": "address" 133 | } 134 | ], 135 | "name": "allowance", 136 | "outputs": [ 137 | { 138 | "internalType": "uint256", 139 | "name": "", 140 | "type": "uint256" 141 | } 142 | ], 143 | "payable": false, 144 | "stateMutability": "view", 145 | "type": "function" 146 | }, 147 | { 148 | "inputs": [ 149 | { 150 | "internalType": "string", 151 | "name": "name", 152 | "type": "string" 153 | }, 154 | { 155 | "internalType": "string", 156 | "name": "symbol", 157 | "type": "string" 158 | }, 159 | { 160 | "internalType": "uint8", 161 | "name": "decimals", 162 | "type": "uint8" 163 | } 164 | ], 165 | "payable": false, 166 | "stateMutability": "nonpayable", 167 | "type": "constructor" 168 | }, 169 | { 170 | "anonymous": false, 171 | "inputs": [ 172 | { 173 | "indexed": true, 174 | "internalType": "address", 175 | "name": "from", 176 | "type": "address" 177 | }, 178 | { 179 | "indexed": true, 180 | "internalType": "address", 181 | "name": "to", 182 | "type": "address" 183 | }, 184 | { 185 | "indexed": false, 186 | "internalType": "uint256", 187 | "name": "value", 188 | "type": "uint256" 189 | } 190 | ], 191 | "name": "Transfer", 192 | "type": "event" 193 | }, 194 | { 195 | "anonymous": false, 196 | "inputs": [ 197 | { 198 | "indexed": true, 199 | "internalType": "address", 200 | "name": "owner", 201 | "type": "address" 202 | }, 203 | { 204 | "indexed": true, 205 | "internalType": "address", 206 | "name": "spender", 207 | "type": "address" 208 | }, 209 | { 210 | "indexed": false, 211 | "internalType": "uint256", 212 | "name": "value", 213 | "type": "uint256" 214 | } 215 | ], 216 | "name": "Approval", 217 | "type": "event" 218 | }, 219 | { 220 | "constant": true, 221 | "inputs": [], 222 | "name": "name", 223 | "outputs": [ 224 | { 225 | "internalType": "string", 226 | "name": "", 227 | "type": "string" 228 | } 229 | ], 230 | "payable": false, 231 | "stateMutability": "view", 232 | "type": "function" 233 | }, 234 | { 235 | "constant": true, 236 | "inputs": [], 237 | "name": "symbol", 238 | "outputs": [ 239 | { 240 | "internalType": "string", 241 | "name": "", 242 | "type": "string" 243 | } 244 | ], 245 | "payable": false, 246 | "stateMutability": "view", 247 | "type": "function" 248 | }, 249 | { 250 | "constant": true, 251 | "inputs": [], 252 | "name": "decimals", 253 | "outputs": [ 254 | { 255 | "internalType": "uint8", 256 | "name": "", 257 | "type": "uint8" 258 | } 259 | ], 260 | "payable": false, 261 | "stateMutability": "view", 262 | "type": "function" 263 | } 264 | ] -------------------------------------------------------------------------------- /frontend/abis/HashTester-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "payable": false, 5 | "stateMutability": "nonpayable", 6 | "type": "constructor" 7 | }, 8 | { 9 | "anonymous": false, 10 | "inputs": [ 11 | { 12 | "indexed": false, 13 | "internalType": "bytes32", 14 | "name": "normal", 15 | "type": "bytes32" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "converted", 21 | "type": "uint256" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "bytes32", 26 | "name": "normal_shifted", 27 | "type": "bytes32" 28 | }, 29 | { 30 | "indexed": false, 31 | "internalType": "uint256", 32 | "name": "converted_shifted", 33 | "type": "uint256" 34 | } 35 | ], 36 | "name": "DebugHash", 37 | "type": "event" 38 | }, 39 | { 40 | "anonymous": false, 41 | "inputs": [ 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "prev_rolling_hash", 46 | "type": "uint256" 47 | }, 48 | { 49 | "indexed": false, 50 | "internalType": "uint256", 51 | "name": "signal_hash", 52 | "type": "uint256" 53 | }, 54 | { 55 | "indexed": false, 56 | "internalType": "uint256", 57 | "name": "rolling_hash", 58 | "type": "uint256" 59 | }, 60 | { 61 | "indexed": false, 62 | "internalType": "bytes", 63 | "name": "encoded", 64 | "type": "bytes" 65 | } 66 | ], 67 | "name": "DebugRollingHash", 68 | "type": "event" 69 | }, 70 | { 71 | "constant": false, 72 | "inputs": [ 73 | { 74 | "internalType": "bytes", 75 | "name": "signal", 76 | "type": "bytes" 77 | } 78 | ], 79 | "name": "Test", 80 | "outputs": [], 81 | "payable": false, 82 | "stateMutability": "nonpayable", 83 | "type": "function" 84 | } 85 | ] -------------------------------------------------------------------------------- /frontend/abis/IERC20-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "from", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "to", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "value", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "Transfer", 25 | "type": "event" 26 | }, 27 | { 28 | "anonymous": false, 29 | "inputs": [ 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "owner", 34 | "type": "address" 35 | }, 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "spender", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "value", 46 | "type": "uint256" 47 | } 48 | ], 49 | "name": "Approval", 50 | "type": "event" 51 | }, 52 | { 53 | "constant": true, 54 | "inputs": [], 55 | "name": "totalSupply", 56 | "outputs": [ 57 | { 58 | "internalType": "uint256", 59 | "name": "", 60 | "type": "uint256" 61 | } 62 | ], 63 | "payable": false, 64 | "stateMutability": "view", 65 | "type": "function" 66 | }, 67 | { 68 | "constant": true, 69 | "inputs": [ 70 | { 71 | "internalType": "address", 72 | "name": "account", 73 | "type": "address" 74 | } 75 | ], 76 | "name": "balanceOf", 77 | "outputs": [ 78 | { 79 | "internalType": "uint256", 80 | "name": "", 81 | "type": "uint256" 82 | } 83 | ], 84 | "payable": false, 85 | "stateMutability": "view", 86 | "type": "function" 87 | }, 88 | { 89 | "constant": false, 90 | "inputs": [ 91 | { 92 | "internalType": "address", 93 | "name": "recipient", 94 | "type": "address" 95 | }, 96 | { 97 | "internalType": "uint256", 98 | "name": "amount", 99 | "type": "uint256" 100 | } 101 | ], 102 | "name": "transfer", 103 | "outputs": [ 104 | { 105 | "internalType": "bool", 106 | "name": "", 107 | "type": "bool" 108 | } 109 | ], 110 | "payable": false, 111 | "stateMutability": "nonpayable", 112 | "type": "function" 113 | }, 114 | { 115 | "constant": true, 116 | "inputs": [ 117 | { 118 | "internalType": "address", 119 | "name": "owner", 120 | "type": "address" 121 | }, 122 | { 123 | "internalType": "address", 124 | "name": "spender", 125 | "type": "address" 126 | } 127 | ], 128 | "name": "allowance", 129 | "outputs": [ 130 | { 131 | "internalType": "uint256", 132 | "name": "", 133 | "type": "uint256" 134 | } 135 | ], 136 | "payable": false, 137 | "stateMutability": "view", 138 | "type": "function" 139 | }, 140 | { 141 | "constant": false, 142 | "inputs": [ 143 | { 144 | "internalType": "address", 145 | "name": "spender", 146 | "type": "address" 147 | }, 148 | { 149 | "internalType": "uint256", 150 | "name": "amount", 151 | "type": "uint256" 152 | } 153 | ], 154 | "name": "approve", 155 | "outputs": [ 156 | { 157 | "internalType": "bool", 158 | "name": "", 159 | "type": "bool" 160 | } 161 | ], 162 | "payable": false, 163 | "stateMutability": "nonpayable", 164 | "type": "function" 165 | }, 166 | { 167 | "constant": false, 168 | "inputs": [ 169 | { 170 | "internalType": "address", 171 | "name": "sender", 172 | "type": "address" 173 | }, 174 | { 175 | "internalType": "address", 176 | "name": "recipient", 177 | "type": "address" 178 | }, 179 | { 180 | "internalType": "uint256", 181 | "name": "amount", 182 | "type": "uint256" 183 | } 184 | ], 185 | "name": "transferFrom", 186 | "outputs": [ 187 | { 188 | "internalType": "bool", 189 | "name": "", 190 | "type": "bool" 191 | } 192 | ], 193 | "payable": false, 194 | "stateMutability": "nonpayable", 195 | "type": "function" 196 | } 197 | ] -------------------------------------------------------------------------------- /frontend/abis/MerkleTree-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "next_index", 6 | "outputs": [ 7 | { 8 | "internalType": "uint32", 9 | "name": "", 10 | "type": "uint32" 11 | } 12 | ], 13 | "payable": false, 14 | "stateMutability": "view", 15 | "type": "function" 16 | }, 17 | { 18 | "constant": true, 19 | "inputs": [ 20 | { 21 | "internalType": "uint256", 22 | "name": "", 23 | "type": "uint256" 24 | } 25 | ], 26 | "name": "filled_subtrees", 27 | "outputs": [ 28 | { 29 | "internalType": "uint256", 30 | "name": "", 31 | "type": "uint256" 32 | } 33 | ], 34 | "payable": false, 35 | "stateMutability": "view", 36 | "type": "function" 37 | }, 38 | { 39 | "constant": true, 40 | "inputs": [ 41 | { 42 | "internalType": "uint256", 43 | "name": "", 44 | "type": "uint256" 45 | } 46 | ], 47 | "name": "zeros", 48 | "outputs": [ 49 | { 50 | "internalType": "uint256", 51 | "name": "", 52 | "type": "uint256" 53 | } 54 | ], 55 | "payable": false, 56 | "stateMutability": "view", 57 | "type": "function" 58 | }, 59 | { 60 | "constant": true, 61 | "inputs": [], 62 | "name": "root", 63 | "outputs": [ 64 | { 65 | "internalType": "uint256", 66 | "name": "", 67 | "type": "uint256" 68 | } 69 | ], 70 | "payable": false, 71 | "stateMutability": "view", 72 | "type": "function" 73 | }, 74 | { 75 | "inputs": [ 76 | { 77 | "internalType": "uint8", 78 | "name": "tree_levels", 79 | "type": "uint8" 80 | }, 81 | { 82 | "internalType": "uint256", 83 | "name": "zero_value", 84 | "type": "uint256" 85 | } 86 | ], 87 | "payable": false, 88 | "stateMutability": "nonpayable", 89 | "type": "constructor" 90 | }, 91 | { 92 | "anonymous": false, 93 | "inputs": [ 94 | { 95 | "indexed": false, 96 | "internalType": "uint256", 97 | "name": "leaf", 98 | "type": "uint256" 99 | }, 100 | { 101 | "indexed": false, 102 | "internalType": "uint32", 103 | "name": "leaf_index", 104 | "type": "uint32" 105 | } 106 | ], 107 | "name": "LeafAdded", 108 | "type": "event" 109 | }, 110 | { 111 | "anonymous": false, 112 | "inputs": [ 113 | { 114 | "indexed": false, 115 | "internalType": "uint256", 116 | "name": "leaf", 117 | "type": "uint256" 118 | }, 119 | { 120 | "indexed": false, 121 | "internalType": "uint32", 122 | "name": "leaf_index", 123 | "type": "uint32" 124 | } 125 | ], 126 | "name": "LeafUpdated", 127 | "type": "event" 128 | }, 129 | { 130 | "constant": true, 131 | "inputs": [ 132 | { 133 | "internalType": "uint256", 134 | "name": "left", 135 | "type": "uint256" 136 | }, 137 | { 138 | "internalType": "uint256", 139 | "name": "right", 140 | "type": "uint256" 141 | } 142 | ], 143 | "name": "HashLeftRight", 144 | "outputs": [ 145 | { 146 | "internalType": "uint256", 147 | "name": "mimc_hash", 148 | "type": "uint256" 149 | } 150 | ], 151 | "payable": false, 152 | "stateMutability": "pure", 153 | "type": "function" 154 | } 155 | ] -------------------------------------------------------------------------------- /frontend/abis/MerkleTreeTester-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "internalType": "uint256", 7 | "name": "left", 8 | "type": "uint256" 9 | }, 10 | { 11 | "internalType": "uint256", 12 | "name": "right", 13 | "type": "uint256" 14 | } 15 | ], 16 | "name": "HashLeftRight", 17 | "outputs": [ 18 | { 19 | "internalType": "uint256", 20 | "name": "mimc_hash", 21 | "type": "uint256" 22 | } 23 | ], 24 | "payable": false, 25 | "stateMutability": "pure", 26 | "type": "function" 27 | }, 28 | { 29 | "constant": true, 30 | "inputs": [], 31 | "name": "next_index", 32 | "outputs": [ 33 | { 34 | "internalType": "uint32", 35 | "name": "", 36 | "type": "uint32" 37 | } 38 | ], 39 | "payable": false, 40 | "stateMutability": "view", 41 | "type": "function" 42 | }, 43 | { 44 | "constant": true, 45 | "inputs": [ 46 | { 47 | "internalType": "uint256", 48 | "name": "", 49 | "type": "uint256" 50 | } 51 | ], 52 | "name": "filled_subtrees", 53 | "outputs": [ 54 | { 55 | "internalType": "uint256", 56 | "name": "", 57 | "type": "uint256" 58 | } 59 | ], 60 | "payable": false, 61 | "stateMutability": "view", 62 | "type": "function" 63 | }, 64 | { 65 | "constant": true, 66 | "inputs": [ 67 | { 68 | "internalType": "uint256", 69 | "name": "", 70 | "type": "uint256" 71 | } 72 | ], 73 | "name": "zeros", 74 | "outputs": [ 75 | { 76 | "internalType": "uint256", 77 | "name": "", 78 | "type": "uint256" 79 | } 80 | ], 81 | "payable": false, 82 | "stateMutability": "view", 83 | "type": "function" 84 | }, 85 | { 86 | "constant": true, 87 | "inputs": [], 88 | "name": "root", 89 | "outputs": [ 90 | { 91 | "internalType": "uint256", 92 | "name": "", 93 | "type": "uint256" 94 | } 95 | ], 96 | "payable": false, 97 | "stateMutability": "view", 98 | "type": "function" 99 | }, 100 | { 101 | "inputs": [], 102 | "payable": false, 103 | "stateMutability": "nonpayable", 104 | "type": "constructor" 105 | }, 106 | { 107 | "anonymous": false, 108 | "inputs": [ 109 | { 110 | "indexed": false, 111 | "internalType": "uint256", 112 | "name": "leaf", 113 | "type": "uint256" 114 | }, 115 | { 116 | "indexed": false, 117 | "internalType": "uint32", 118 | "name": "leaf_index", 119 | "type": "uint32" 120 | } 121 | ], 122 | "name": "LeafAdded", 123 | "type": "event" 124 | }, 125 | { 126 | "anonymous": false, 127 | "inputs": [ 128 | { 129 | "indexed": false, 130 | "internalType": "uint256", 131 | "name": "leaf", 132 | "type": "uint256" 133 | }, 134 | { 135 | "indexed": false, 136 | "internalType": "uint32", 137 | "name": "leaf_index", 138 | "type": "uint32" 139 | } 140 | ], 141 | "name": "LeafUpdated", 142 | "type": "event" 143 | }, 144 | { 145 | "constant": false, 146 | "inputs": [ 147 | { 148 | "internalType": "uint256", 149 | "name": "leaf", 150 | "type": "uint256" 151 | } 152 | ], 153 | "name": "insert_test", 154 | "outputs": [], 155 | "payable": false, 156 | "stateMutability": "nonpayable", 157 | "type": "function" 158 | } 159 | ] -------------------------------------------------------------------------------- /frontend/abis/MiMC-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "internalType": "uint256", 7 | "name": "in_xL", 8 | "type": "uint256" 9 | }, 10 | { 11 | "internalType": "uint256", 12 | "name": "in_xR", 13 | "type": "uint256" 14 | }, 15 | { 16 | "internalType": "uint256", 17 | "name": "in_k", 18 | "type": "uint256" 19 | } 20 | ], 21 | "name": "MiMCSponge", 22 | "outputs": [ 23 | { 24 | "internalType": "uint256", 25 | "name": "xL", 26 | "type": "uint256" 27 | }, 28 | { 29 | "internalType": "uint256", 30 | "name": "xR", 31 | "type": "uint256" 32 | } 33 | ], 34 | "payable": false, 35 | "stateMutability": "pure", 36 | "type": "function" 37 | } 38 | ] -------------------------------------------------------------------------------- /frontend/abis/Migrations-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "last_completed_migration", 6 | "outputs": [ 7 | { 8 | "internalType": "uint256", 9 | "name": "", 10 | "type": "uint256" 11 | } 12 | ], 13 | "payable": false, 14 | "stateMutability": "view", 15 | "type": "function" 16 | }, 17 | { 18 | "constant": true, 19 | "inputs": [], 20 | "name": "owner", 21 | "outputs": [ 22 | { 23 | "internalType": "address", 24 | "name": "", 25 | "type": "address" 26 | } 27 | ], 28 | "payable": false, 29 | "stateMutability": "view", 30 | "type": "function" 31 | }, 32 | { 33 | "inputs": [], 34 | "payable": false, 35 | "stateMutability": "nonpayable", 36 | "type": "constructor" 37 | }, 38 | { 39 | "constant": false, 40 | "inputs": [ 41 | { 42 | "internalType": "uint256", 43 | "name": "completed", 44 | "type": "uint256" 45 | } 46 | ], 47 | "name": "setCompleted", 48 | "outputs": [], 49 | "payable": false, 50 | "stateMutability": "nonpayable", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "internalType": "address", 58 | "name": "new_address", 59 | "type": "address" 60 | } 61 | ], 62 | "name": "upgrade", 63 | "outputs": [], 64 | "payable": false, 65 | "stateMutability": "nonpayable", 66 | "type": "function" 67 | } 68 | ] -------------------------------------------------------------------------------- /frontend/abis/MinterRole-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "payable": false, 5 | "stateMutability": "nonpayable", 6 | "type": "constructor" 7 | }, 8 | { 9 | "anonymous": false, 10 | "inputs": [ 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "account", 15 | "type": "address" 16 | } 17 | ], 18 | "name": "MinterAdded", 19 | "type": "event" 20 | }, 21 | { 22 | "anonymous": false, 23 | "inputs": [ 24 | { 25 | "indexed": true, 26 | "internalType": "address", 27 | "name": "account", 28 | "type": "address" 29 | } 30 | ], 31 | "name": "MinterRemoved", 32 | "type": "event" 33 | }, 34 | { 35 | "constant": true, 36 | "inputs": [ 37 | { 38 | "internalType": "address", 39 | "name": "account", 40 | "type": "address" 41 | } 42 | ], 43 | "name": "isMinter", 44 | "outputs": [ 45 | { 46 | "internalType": "bool", 47 | "name": "", 48 | "type": "bool" 49 | } 50 | ], 51 | "payable": false, 52 | "stateMutability": "view", 53 | "type": "function" 54 | }, 55 | { 56 | "constant": false, 57 | "inputs": [ 58 | { 59 | "internalType": "address", 60 | "name": "account", 61 | "type": "address" 62 | } 63 | ], 64 | "name": "addMinter", 65 | "outputs": [], 66 | "payable": false, 67 | "stateMutability": "nonpayable", 68 | "type": "function" 69 | }, 70 | { 71 | "constant": false, 72 | "inputs": [], 73 | "name": "renounceMinter", 74 | "outputs": [], 75 | "payable": false, 76 | "stateMutability": "nonpayable", 77 | "type": "function" 78 | } 79 | ] -------------------------------------------------------------------------------- /frontend/abis/Mixer-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "mixAmt", 6 | "outputs": [ 7 | { 8 | "internalType": "uint256", 9 | "name": "", 10 | "type": "uint256" 11 | } 12 | ], 13 | "payable": false, 14 | "stateMutability": "view", 15 | "type": "function" 16 | }, 17 | { 18 | "constant": true, 19 | "inputs": [], 20 | "name": "semaphore", 21 | "outputs": [ 22 | { 23 | "internalType": "contract Semaphore", 24 | "name": "", 25 | "type": "address" 26 | } 27 | ], 28 | "payable": false, 29 | "stateMutability": "view", 30 | "type": "function" 31 | }, 32 | { 33 | "constant": true, 34 | "inputs": [], 35 | "name": "token", 36 | "outputs": [ 37 | { 38 | "internalType": "contract IERC20", 39 | "name": "", 40 | "type": "address" 41 | } 42 | ], 43 | "payable": false, 44 | "stateMutability": "view", 45 | "type": "function" 46 | }, 47 | { 48 | "inputs": [ 49 | { 50 | "internalType": "address", 51 | "name": "_semaphore", 52 | "type": "address" 53 | }, 54 | { 55 | "internalType": "uint256", 56 | "name": "_mixAmt", 57 | "type": "uint256" 58 | }, 59 | { 60 | "internalType": "address", 61 | "name": "_token", 62 | "type": "address" 63 | } 64 | ], 65 | "payable": false, 66 | "stateMutability": "nonpayable", 67 | "type": "constructor" 68 | }, 69 | { 70 | "anonymous": false, 71 | "inputs": [ 72 | { 73 | "indexed": true, 74 | "internalType": "address", 75 | "name": "depositor", 76 | "type": "address" 77 | }, 78 | { 79 | "indexed": true, 80 | "internalType": "uint256", 81 | "name": "mixAmt", 82 | "type": "uint256" 83 | }, 84 | { 85 | "indexed": false, 86 | "internalType": "uint256", 87 | "name": "identityCommitment", 88 | "type": "uint256" 89 | } 90 | ], 91 | "name": "Deposited", 92 | "type": "event" 93 | }, 94 | { 95 | "anonymous": false, 96 | "inputs": [ 97 | { 98 | "indexed": true, 99 | "internalType": "address", 100 | "name": "depositor", 101 | "type": "address" 102 | }, 103 | { 104 | "indexed": true, 105 | "internalType": "uint256", 106 | "name": "mixAmt", 107 | "type": "uint256" 108 | }, 109 | { 110 | "indexed": false, 111 | "internalType": "uint256", 112 | "name": "identityCommitment", 113 | "type": "uint256" 114 | } 115 | ], 116 | "name": "DepositedERC20", 117 | "type": "event" 118 | }, 119 | { 120 | "anonymous": false, 121 | "inputs": [ 122 | { 123 | "indexed": true, 124 | "internalType": "address", 125 | "name": "recipient", 126 | "type": "address" 127 | }, 128 | { 129 | "indexed": true, 130 | "internalType": "uint256", 131 | "name": "mixAmt", 132 | "type": "uint256" 133 | }, 134 | { 135 | "indexed": true, 136 | "internalType": "uint256", 137 | "name": "operatorFeeEarned", 138 | "type": "uint256" 139 | } 140 | ], 141 | "name": "Mixed", 142 | "type": "event" 143 | }, 144 | { 145 | "anonymous": false, 146 | "inputs": [ 147 | { 148 | "indexed": true, 149 | "internalType": "address", 150 | "name": "recipient", 151 | "type": "address" 152 | }, 153 | { 154 | "indexed": true, 155 | "internalType": "uint256", 156 | "name": "mixAmt", 157 | "type": "uint256" 158 | }, 159 | { 160 | "indexed": true, 161 | "internalType": "uint256", 162 | "name": "operatorFeeEarned", 163 | "type": "uint256" 164 | } 165 | ], 166 | "name": "MixedERC20", 167 | "type": "event" 168 | }, 169 | { 170 | "constant": true, 171 | "inputs": [], 172 | "name": "supportsEthOnly", 173 | "outputs": [ 174 | { 175 | "internalType": "bool", 176 | "name": "", 177 | "type": "bool" 178 | } 179 | ], 180 | "payable": false, 181 | "stateMutability": "view", 182 | "type": "function" 183 | }, 184 | { 185 | "constant": false, 186 | "inputs": [], 187 | "name": "setSemaphoreExternalNulllifier", 188 | "outputs": [], 189 | "payable": false, 190 | "stateMutability": "nonpayable", 191 | "type": "function" 192 | }, 193 | { 194 | "constant": true, 195 | "inputs": [], 196 | "name": "getLeaves", 197 | "outputs": [ 198 | { 199 | "internalType": "uint256[]", 200 | "name": "", 201 | "type": "uint256[]" 202 | } 203 | ], 204 | "payable": false, 205 | "stateMutability": "view", 206 | "type": "function" 207 | }, 208 | { 209 | "constant": false, 210 | "inputs": [ 211 | { 212 | "internalType": "uint256", 213 | "name": "_identityCommitment", 214 | "type": "uint256" 215 | } 216 | ], 217 | "name": "depositERC20", 218 | "outputs": [], 219 | "payable": false, 220 | "stateMutability": "nonpayable", 221 | "type": "function" 222 | }, 223 | { 224 | "constant": false, 225 | "inputs": [ 226 | { 227 | "internalType": "uint256", 228 | "name": "_identityCommitment", 229 | "type": "uint256" 230 | } 231 | ], 232 | "name": "deposit", 233 | "outputs": [], 234 | "payable": true, 235 | "stateMutability": "payable", 236 | "type": "function" 237 | }, 238 | { 239 | "constant": false, 240 | "inputs": [ 241 | { 242 | "components": [ 243 | { 244 | "internalType": "bytes32", 245 | "name": "signal", 246 | "type": "bytes32" 247 | }, 248 | { 249 | "internalType": "uint256[2]", 250 | "name": "a", 251 | "type": "uint256[2]" 252 | }, 253 | { 254 | "internalType": "uint256[2][2]", 255 | "name": "b", 256 | "type": "uint256[2][2]" 257 | }, 258 | { 259 | "internalType": "uint256[2]", 260 | "name": "c", 261 | "type": "uint256[2]" 262 | }, 263 | { 264 | "internalType": "uint256[4]", 265 | "name": "input", 266 | "type": "uint256[4]" 267 | }, 268 | { 269 | "internalType": "address payable", 270 | "name": "recipientAddress", 271 | "type": "address" 272 | }, 273 | { 274 | "internalType": "uint256", 275 | "name": "fee", 276 | "type": "uint256" 277 | } 278 | ], 279 | "internalType": "struct Mixer.DepositProof", 280 | "name": "_proof", 281 | "type": "tuple" 282 | }, 283 | { 284 | "internalType": "address payable", 285 | "name": "_relayerAddress", 286 | "type": "address" 287 | } 288 | ], 289 | "name": "mixERC20", 290 | "outputs": [], 291 | "payable": false, 292 | "stateMutability": "nonpayable", 293 | "type": "function" 294 | }, 295 | { 296 | "constant": false, 297 | "inputs": [ 298 | { 299 | "components": [ 300 | { 301 | "internalType": "bytes32", 302 | "name": "signal", 303 | "type": "bytes32" 304 | }, 305 | { 306 | "internalType": "uint256[2]", 307 | "name": "a", 308 | "type": "uint256[2]" 309 | }, 310 | { 311 | "internalType": "uint256[2][2]", 312 | "name": "b", 313 | "type": "uint256[2][2]" 314 | }, 315 | { 316 | "internalType": "uint256[2]", 317 | "name": "c", 318 | "type": "uint256[2]" 319 | }, 320 | { 321 | "internalType": "uint256[4]", 322 | "name": "input", 323 | "type": "uint256[4]" 324 | }, 325 | { 326 | "internalType": "address payable", 327 | "name": "recipientAddress", 328 | "type": "address" 329 | }, 330 | { 331 | "internalType": "uint256", 332 | "name": "fee", 333 | "type": "uint256" 334 | } 335 | ], 336 | "internalType": "struct Mixer.DepositProof", 337 | "name": "_proof", 338 | "type": "tuple" 339 | }, 340 | { 341 | "internalType": "address payable", 342 | "name": "_relayerAddress", 343 | "type": "address" 344 | } 345 | ], 346 | "name": "mix", 347 | "outputs": [], 348 | "payable": false, 349 | "stateMutability": "nonpayable", 350 | "type": "function" 351 | } 352 | ] -------------------------------------------------------------------------------- /frontend/abis/MultipleMerkleTree-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": false, 7 | "internalType": "uint8", 8 | "name": "tree_index", 9 | "type": "uint8" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "uint256", 14 | "name": "leaf", 15 | "type": "uint256" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint32", 20 | "name": "leaf_index", 21 | "type": "uint32" 22 | } 23 | ], 24 | "name": "LeafAdded", 25 | "type": "event" 26 | }, 27 | { 28 | "anonymous": false, 29 | "inputs": [ 30 | { 31 | "indexed": false, 32 | "internalType": "uint8", 33 | "name": "tree_index", 34 | "type": "uint8" 35 | }, 36 | { 37 | "indexed": false, 38 | "internalType": "uint256", 39 | "name": "leaf", 40 | "type": "uint256" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint32", 45 | "name": "leaf_index", 46 | "type": "uint32" 47 | } 48 | ], 49 | "name": "LeafUpdated", 50 | "type": "event" 51 | }, 52 | { 53 | "constant": false, 54 | "inputs": [ 55 | { 56 | "internalType": "uint8", 57 | "name": "tree_levels", 58 | "type": "uint8" 59 | }, 60 | { 61 | "internalType": "uint256", 62 | "name": "zero_value", 63 | "type": "uint256" 64 | } 65 | ], 66 | "name": "init_tree", 67 | "outputs": [ 68 | { 69 | "internalType": "uint8", 70 | "name": "tree_index", 71 | "type": "uint8" 72 | } 73 | ], 74 | "payable": false, 75 | "stateMutability": "nonpayable", 76 | "type": "function" 77 | }, 78 | { 79 | "constant": true, 80 | "inputs": [ 81 | { 82 | "internalType": "uint256", 83 | "name": "left", 84 | "type": "uint256" 85 | }, 86 | { 87 | "internalType": "uint256", 88 | "name": "right", 89 | "type": "uint256" 90 | } 91 | ], 92 | "name": "HashLeftRight", 93 | "outputs": [ 94 | { 95 | "internalType": "uint256", 96 | "name": "mimc_hash", 97 | "type": "uint256" 98 | } 99 | ], 100 | "payable": false, 101 | "stateMutability": "pure", 102 | "type": "function" 103 | } 104 | ] -------------------------------------------------------------------------------- /frontend/abis/Ownable-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "payable": false, 5 | "stateMutability": "nonpayable", 6 | "type": "constructor" 7 | }, 8 | { 9 | "anonymous": false, 10 | "inputs": [ 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "previousOwner", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": true, 19 | "internalType": "address", 20 | "name": "newOwner", 21 | "type": "address" 22 | } 23 | ], 24 | "name": "OwnershipTransferred", 25 | "type": "event" 26 | }, 27 | { 28 | "constant": true, 29 | "inputs": [], 30 | "name": "owner", 31 | "outputs": [ 32 | { 33 | "internalType": "address", 34 | "name": "", 35 | "type": "address" 36 | } 37 | ], 38 | "payable": false, 39 | "stateMutability": "view", 40 | "type": "function" 41 | }, 42 | { 43 | "constant": true, 44 | "inputs": [], 45 | "name": "isOwner", 46 | "outputs": [ 47 | { 48 | "internalType": "bool", 49 | "name": "", 50 | "type": "bool" 51 | } 52 | ], 53 | "payable": false, 54 | "stateMutability": "view", 55 | "type": "function" 56 | }, 57 | { 58 | "constant": false, 59 | "inputs": [], 60 | "name": "renounceOwnership", 61 | "outputs": [], 62 | "payable": false, 63 | "stateMutability": "nonpayable", 64 | "type": "function" 65 | }, 66 | { 67 | "constant": false, 68 | "inputs": [ 69 | { 70 | "internalType": "address", 71 | "name": "newOwner", 72 | "type": "address" 73 | } 74 | ], 75 | "name": "transferOwnership", 76 | "outputs": [], 77 | "payable": false, 78 | "stateMutability": "nonpayable", 79 | "type": "function" 80 | } 81 | ] -------------------------------------------------------------------------------- /frontend/abis/Pairing-abi.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /frontend/abis/RelayerRegistry-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": false, 4 | "inputs": [ 5 | { 6 | "internalType": "address", 7 | "name": "_applicationContract", 8 | "type": "address" 9 | }, 10 | { 11 | "internalType": "bytes", 12 | "name": "_encodedPayload", 13 | "type": "bytes" 14 | } 15 | ], 16 | "name": "relayCall", 17 | "outputs": [], 18 | "payable": true, 19 | "stateMutability": "payable", 20 | "type": "function" 21 | } 22 | ] -------------------------------------------------------------------------------- /frontend/abis/Roles-abi.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /frontend/abis/SafeMath-abi.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /frontend/abis/Verifier-abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "internalType": "uint256[2]", 7 | "name": "a", 8 | "type": "uint256[2]" 9 | }, 10 | { 11 | "internalType": "uint256[2][2]", 12 | "name": "b", 13 | "type": "uint256[2][2]" 14 | }, 15 | { 16 | "internalType": "uint256[2]", 17 | "name": "c", 18 | "type": "uint256[2]" 19 | }, 20 | { 21 | "internalType": "uint256[4]", 22 | "name": "input", 23 | "type": "uint256[4]" 24 | } 25 | ], 26 | "name": "verifyProof", 27 | "outputs": [ 28 | { 29 | "internalType": "bool", 30 | "name": "r", 31 | "type": "bool" 32 | } 33 | ], 34 | "payable": false, 35 | "stateMutability": "view", 36 | "type": "function" 37 | } 38 | ] -------------------------------------------------------------------------------- /frontend/exported_config.json: -------------------------------------------------------------------------------- 1 | {"env":"local-dev","mixAmtEth":"0.1","feeAmtEth":"0.001","mixAmtTokens":100,"feeAmtTokens":1,"tokenSym":"DAI","tokenDecimals":18,"chain":{"url":"http://localhost:8545","chainId":1234,"mix":{"gasLimit":1000000},"privateKeysPath":"../ganachePrivateKeys.json","deployedAddresses":{"MiMC":"0xF12b5dd4EAD5F743C6BaA640B0216200e89B60Da","Semaphore":"0x345cA3e014Aaf5dcA488057592ee47305D9B3e10","Mixer":"0xf25186B5081Ff5cE73482AD761DB0eB0d25abfBF","TokenMixer":"0x30753E4A8aad7F8597332E813735Def5dD395028","TokenSemaphore":"0x2C2B9C9a4a25e24B174f26114e8926a9f2128FE4","RelayerRegistry":"0xf204a4Ef082f5c04bB89F7D5E6568B796096735a","Token":"0x8CdaF0CD259887258Bc13a92C0a6dA92698644C0"}},"backend":{"port":3000,"host":"http://localhost","hotWalletPrivKeyPath":"/home/di/MIXER_SECRETS/hotWalletPrivKey_ganache.json","relayerAddress":"0x627306090abaB3A6e1400e9345bC60c78a8BEf57","etcd":{"host":"localhost","port":2379,"lockTime":7000},"testing":{"privKeys":["0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3","0xae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f"]}},"frontend":{"snarks":{"paths":{"verificationKey":"http://localhost:8000/build/verification_key.json","circuit":"http://localhost:8000/build/circuit.json","provingKey":"http://localhost:8000/build/proving_key.bin"}},"countdown":{"endsAtUtcMidnight":false,"endsAfterSecs":2},"blockExplorerTxPrefix":"https://kovan.etherscan.io/tx/","supportedNetworkName":"local","supportedNetwork":1234}} 2 | -------------------------------------------------------------------------------- /frontend/externals/worker_threads.js: -------------------------------------------------------------------------------- 1 | module.exports = window.worker_threads; 2 | -------------------------------------------------------------------------------- /frontend/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/favicons/favicon.ico -------------------------------------------------------------------------------- /frontend/img/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/img/cat.png -------------------------------------------------------------------------------- /frontend/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/img/logo.png -------------------------------------------------------------------------------- /frontend/img/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weijiekoh/mixer/efbe90eaf95bbe431fb355954d9e0c46098b3fba/frontend/img/og.png -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | MicroMix 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/less/components/index.less: -------------------------------------------------------------------------------- 1 | @import (less) './walletWidget.less'; 2 | -------------------------------------------------------------------------------- /frontend/less/components/walletWidget.less: -------------------------------------------------------------------------------- 1 | /*@import (less) '../constants.less';*/ 2 | 3 | #wallet-widget { 4 | .circle-icon { 5 | width: 10px; 6 | margin-right: 8px; 7 | 8 | &.ok { 9 | fill: @bulma-success; 10 | } 11 | 12 | &.fail { 13 | fill: @bulma-failure; 14 | } 15 | 16 | &.warn { 17 | fill: @bulma-warn; 18 | } 19 | } 20 | 21 | @media screen and (max-width: @bulma-mobile) { 22 | .address { 23 | display:inline-block; 24 | white-space: nowrap; 25 | overflow: hidden; 26 | text-overflow: ellipsis; 27 | max-width: 10ch; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/less/constants.less: -------------------------------------------------------------------------------- 1 | @bulma-mobile: 768px; 2 | @bulma-tablet: 1023px; 3 | 4 | @bulma-success: hsl(141, 71%, 48%); 5 | @bulma-failure: hsl(348, 100%, 61%); 6 | @bulma-warn: hsl(48, 100%, 67%); 7 | -------------------------------------------------------------------------------- /frontend/less/index.less: -------------------------------------------------------------------------------- 1 | /* 2 | * Stylesheets for the mixer UI 3 | */ 4 | @import (less) '../node_modules/bulma/css/bulma.css'; 5 | @import (less) './constants.less'; 6 | @import (less) './components/index.less'; 7 | @import (less) './routes/index.less'; 8 | 9 | input.eth_address { 10 | font-family: monospace; 11 | } 12 | 13 | .first-section { 14 | padding-top:2em; 15 | padding-bottom:0em; 16 | } 17 | 18 | @media screen and (max-width: @bulma-mobile) { 19 | .navbar { 20 | #wallet-widget { 21 | text-align: center; 22 | } 23 | 24 | #options-link { 25 | display:none; 26 | } 27 | 28 | .navbar-dropdown { 29 | font-size: 1rem; 30 | padding-top: 0; 31 | 32 | .navbar-item { 33 | padding-left: 0.75em; 34 | 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /frontend/less/routes/countdown.less: -------------------------------------------------------------------------------- 1 | .advanced, .chevron-up, .chevron-down { 2 | cursor: pointer; 3 | } 4 | 5 | .chevron-down::after { 6 | border: 3px solid; 7 | border-radius: 2px; 8 | border-right: 0; 9 | border-top: 0; 10 | content: " "; 11 | display: inline-block; 12 | height: 0.625em; 13 | margin-top: -0.4375em; 14 | pointer-events: none; 15 | /*position: absolute;*/ 16 | /*top: 50%;*/ 17 | cursor: pointer; 18 | margin-left: 15px; 19 | -webkit-transform: rotate(-45deg); 20 | transform: rotate(-45deg); 21 | -webkit-transform-origin: center; 22 | transform-origin: center; 23 | width: 0.625em; 24 | } 25 | 26 | .chevron-up::after { 27 | border: 3px solid; 28 | border-radius: 2px; 29 | border-right: 0; 30 | border-top: 0; 31 | content: " "; 32 | display: inline-block; 33 | height: 0.625em; 34 | margin-top: 0.4375em; 35 | pointer-events: none; 36 | margin-left: 37px; 37 | -webkit-transform: rotate(135deg); 38 | transform: rotate(135deg); 39 | -webkit-transform-origin: left; 40 | transform-origin: left; 41 | width: 0.625em; 42 | } 43 | -------------------------------------------------------------------------------- /frontend/less/routes/deposit.less: -------------------------------------------------------------------------------- 1 | .titles { 2 | padding-top:2em; 3 | } 4 | 5 | .sendTo { 6 | span { 7 | margin-right:0.4em; 8 | margin-left:0.4em; 9 | position:relative; 10 | top: 0.4em; 11 | } 12 | } 13 | 14 | .token-select{ 15 | display:inline; 16 | 17 | label { 18 | margin-right: 1em; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/less/routes/index.less: -------------------------------------------------------------------------------- 1 | @import (less) "./quickWithdraw.less"; 2 | @import (less) "./deposit.less"; 3 | @import (less) "./countdown.less"; 4 | -------------------------------------------------------------------------------- /frontend/less/routes/quickWithdraw.less: -------------------------------------------------------------------------------- 1 | .consent_checkbox { 2 | margin-right: 1em; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | worker_rlimit_nofile 8192; 8 | events { 9 | worker_connections 8000; 10 | } 11 | 12 | 13 | http { 14 | server_tokens off; 15 | include /etc/nginx/mime.types; 16 | default_type application/octet-stream; 17 | charset_types 18 | text/css 19 | text/plain 20 | text/vnd.wap.wml 21 | application/javascript 22 | application/json 23 | application/rss+xml 24 | application/xml; 25 | 26 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 27 | '$status $body_bytes_sent "$http_referer" ' 28 | '"$http_user_agent" "$http_x_forwarded_for"'; 29 | 30 | access_log /var/log/nginx/access.log main; 31 | tcp_nopush on; 32 | 33 | sendfile on; 34 | keepalive_timeout 20s; 35 | 36 | gzip on; 37 | gzip_disable "msie6"; 38 | gzip_comp_level 6; 39 | gzip_vary on; 40 | gzip_proxied any; 41 | gzip_buffers 16 8k; 42 | gzip_http_version 1.1; 43 | gzip_min_length 256; 44 | gzip_types 45 | application/octet-stream 46 | application/atom+xml 47 | application/javascript 48 | application/json 49 | application/ld+json 50 | application/manifest+json 51 | application/rss+xml 52 | application/vnd.geo+json 53 | application/vnd.ms-fontobject 54 | application/x-font-ttf 55 | application/x-web-app-manifest+json 56 | application/xhtml+xml 57 | application/xml 58 | font/opentype 59 | image/bmp 60 | image/svg+xml 61 | image/x-icon 62 | text/cache-manifest 63 | text/css 64 | text/plain 65 | text/vcard 66 | text/vnd.rim.location.xloc 67 | text/vtt 68 | text/x-component 69 | text/x-cross-domain-policy; 70 | 71 | limit_req_zone $binary_remote_addr zone=mylimit:10m rate=30r/s; 72 | 73 | server { 74 | server_tokens off; 75 | listen 8001; 76 | server_name mixer-frontend; 77 | real_ip_header X-Real-IP; 78 | index index.html; 79 | 80 | location / { 81 | root /static; 82 | try_files $uri /index.html; 83 | gzip_static on; 84 | } 85 | 86 | location /api { 87 | limit_req zone=mylimit burst=30 nodelay; 88 | proxy_set_header Host $host; 89 | proxy_set_header X-Real-IP $remote_addr; 90 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 91 | proxy_pass http://mixer-backend:3000/; 92 | } 93 | } 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mixer-frontend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "watch": "tsc --watch", 8 | "build": "tsc", 9 | "webpack-watch": "webpack-dev-server", 10 | "webpack-build": "echo 'Building frontend' && NODE_ENV=production webpack && cp -r favicons dist/", 11 | "lint": "tslint --project . 'js/**/*.ts[x]'", 12 | "lint-fix": "tslint --project . 'js/**/*.ts[x]' --fix" 13 | }, 14 | "keywords": [], 15 | "proxy": "http://localhost:8000", 16 | "author": "Koh Wei Jie", 17 | "license": "GPL-3.0-or-later", 18 | "alias": { 19 | "worker_threads": "./externals/worker_threads.js" 20 | }, 21 | "dependencies": { 22 | "buffer": "^5.2.1", 23 | "bulma": "^0.7.5", 24 | "ethers": "^4.0.30", 25 | "mixer-contracts": "1.0.0", 26 | "libsemaphore": "^0.0.16", 27 | "mixer-utils": "1.0.0", 28 | "react": "^16.8.6", 29 | "react-dom": "^16.8.6", 30 | "react-router-dom": "^5.0.1", 31 | "react-timer-hook": "^1.1.6", 32 | "web3-react": "^5.0.4" 33 | }, 34 | "devDependencies": { 35 | "css-loader": "^3.2.0", 36 | "file-loader": "^4.2.0", 37 | "html-webpack-plugin": "^3.2.0", 38 | "less": "^3.10.3", 39 | "less-loader": "^5.0.0", 40 | "style-loader": "^1.0.0", 41 | "terser-webpack-plugin": "^2.1.3", 42 | "tslint": "^5.17.0", 43 | "tslint-microsoft-contrib": "^6.2.0", 44 | "typescript": "^3.5.2", 45 | "webpack": "^4.41.2", 46 | "webpack-cli": "^3.3.9" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/server.js: -------------------------------------------------------------------------------- 1 | // From https://www.npmjs.com/package/parcel-proxy-server 2 | 3 | const ParcelProxyServer = require('parcel-proxy-server'); 4 | 5 | // configure the proxy server 6 | const server = new ParcelProxyServer({ 7 | entryPoint: './index.html', 8 | parcelOptions: { 9 | // provide parcel options here 10 | // these are directly passed into the 11 | // parcel bundler 12 | // 13 | // More info on supported options are documented at 14 | // https://parceljs.org/api 15 | https: false, 16 | autoinstall: false, 17 | }, 18 | proxies: { 19 | '/build': { 20 | target: 'http://localhost:8000/' 21 | }, 22 | '/api': { 23 | target: 'http://localhost:3000/' 24 | } 25 | } 26 | }); 27 | 28 | // the underlying parcel bundler is exposed on the server 29 | // and can be used if needed 30 | server.bundler.on('buildEnd', () => { 31 | console.log('Build completed!'); 32 | }); 33 | 34 | // start up the server 35 | server.listen(1234, () => { 36 | console.log('Parcel proxy server has started'); 37 | }); 38 | -------------------------------------------------------------------------------- /frontend/ts/components/txButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, Component } from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | enum TxButtonStatus { 5 | Default, Loading, Disabled, 6 | } 7 | 8 | enum TxStatuses { 9 | None, Pending, Mined, Err, 10 | } 11 | 12 | const TxButton = ({ 13 | onClick, 14 | label, 15 | txStatus, 16 | isDisabled, 17 | }) => { 18 | let className = 'button is-large is-primary ' 19 | 20 | if (txStatus === TxStatuses.Pending) { 21 | className += 'is-loading ' 22 | isDisabled = true 23 | 24 | } else if (txStatus === TxStatuses.Mined) { 25 | isDisabled = true 26 | } 27 | 28 | const handleClick = () => { 29 | if (isDisabled) { 30 | return 31 | } 32 | onClick() 33 | } 34 | 35 | return ( 36 | // @ts-ignore 37 | 41 | 42 | {label} 43 | 44 | 45 | ) 46 | } 47 | 48 | const Erc20ApproveButton = ({ 49 | onClick, 50 | label, 51 | txStatus, 52 | isDisabled, 53 | }) => { 54 | let className = 'button is-link ' 55 | if (txStatus === TxStatuses.Pending) { 56 | className += 'is-loading ' 57 | isDisabled = true 58 | 59 | } else if (txStatus === TxStatuses.Mined) { 60 | isDisabled = true 61 | } 62 | 63 | const handleClick = () => { 64 | if (isDisabled) { 65 | return 66 | } 67 | onClick() 68 | } 69 | 70 | return ( 71 | // @ts-ignore 72 | 76 | 77 | {label} 78 | 79 | 80 | ) 81 | } 82 | 83 | export { Erc20ApproveButton, TxButton, TxStatuses, TxButtonStatus } 84 | -------------------------------------------------------------------------------- /frontend/ts/components/txHashMessage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, Component } from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { TxStatuses } from './txButton' 4 | const config = require('../../exported_config') 5 | const blockExplorerTxPrefix = config.frontend.blockExplorerTxPrefix 6 | 7 | const TxHashMessage = ({ 8 | txStatus, 9 | txHash, 10 | mixSuccessful, 11 | }) => { 12 | let msg: string = '' 13 | let articleClass: string = '' 14 | 15 | if (mixSuccessful) { 16 | msg = 'Mix successful.' 17 | articleClass = 'is-success' 18 | } else if (txStatus === TxStatuses.Pending) { 19 | msg = 'Transaction pending.' 20 | articleClass = 'is-info' 21 | } else if (txStatus === TxStatuses.Mined) { 22 | msg = 'Transaction mined.' 23 | articleClass = 'is-success' 24 | } 25 | 26 | return ( 27 | 35 | ) 36 | } 37 | 38 | export { TxHashMessage } 39 | -------------------------------------------------------------------------------- /frontend/ts/components/walletWidget.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { useWeb3Context, Connectors } from 'web3-react' 4 | const config = require('../../exported_config') 5 | 6 | const networkName = config.frontend.supportedNetworkName 7 | 8 | const walletLearnUrl = 'https://ethereum.org/use/#_3-what-is-a-wallet' + 9 | '-and-which-one-should-i-use' 10 | 11 | // From Font Awesome 12 | const circleIcon = (className: string) => ( 13 | 14 | 15 | 16 | ) 17 | 18 | const WalletWidget = () => { 19 | const context = useWeb3Context() 20 | 21 | const setConnector = () => { 22 | try { 23 | if (!context.connector) { 24 | context.setConnector('MetaMask') 25 | } 26 | } catch (e) { 27 | } 28 | } 29 | 30 | setConnector() 31 | 32 | const render = () => { 33 | // @ts-ignore 34 | if (!window.hasOwnProperty('ethereum')) { 35 | return ( 36 |

37 | { circleIcon('fail') } 38 | Please install an 40 | Ethereum wallet. 41 | 42 |

43 | ) 44 | } else if (context.active && !context.error) { 45 | return ( 46 |

47 | 48 | { circleIcon('ok') } 49 | { context.account } 50 | 51 |

52 | ) 53 | } else if (context.error != null && context.error['code'] === 'UNSUPPORTED_NETWORK') { 54 | return ( 55 |

56 | { circleIcon('warn') } 57 | Please connect to the {networkName} testnet. 58 |

59 | ) 60 | } else { 61 | return ( 62 |

{setConnector()}} > 65 | Connect wallet 66 |

67 | ) 68 | } 69 | } 70 | 71 | return ( 72 |
73 | {render()} 74 |
75 | ) 76 | } 77 | 78 | export default WalletWidget 79 | -------------------------------------------------------------------------------- /frontend/ts/errors.ts: -------------------------------------------------------------------------------- 1 | enum ErrorCodes { 2 | WITNESS_GEN_ERROR, 3 | INVALID_WITNESS, 4 | INVALID_PROOF, 5 | INVALID_SIG, 6 | TX_FAILED, 7 | PRE_BROADCAST_CHECK_FAILED, 8 | } 9 | 10 | export { 11 | ErrorCodes, 12 | } 13 | -------------------------------------------------------------------------------- /frontend/ts/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { BrowserRouter as Router, Route } from 'react-router-dom' 4 | import Web3Provider from 'web3-react' 5 | 6 | import Nav from './nav' 7 | import AboutRoute from './routes/about' 8 | import DepositRoute from './routes/deposit' 9 | import CountdownRoute from './routes/countdown' 10 | import QuickWithdrawRoute from './routes/quickWithdraw' 11 | import connectors from './web3' 12 | import '../less/index.less' 13 | 14 | import { 15 | initStorage, 16 | } from './storage' 17 | 18 | const App = () => { 19 | initStorage() 20 | return ( 21 | 22 |
23 | 24 |
37 |
38 | ) 39 | } 40 | 41 | const root = document.getElementById('root') 42 | 43 | ReactDOM.render(, root) 44 | -------------------------------------------------------------------------------- /frontend/ts/nav.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import ReactDOM from 'react-dom' 3 | // @ts-ignore 4 | import logo from '../img/logo.png' 5 | 6 | import WalletWidget from './components/walletWidget' 7 | 8 | const Nav = () => { 9 | const [menuToggle, setMenuToggle] = useState(false) 10 | 11 | const burgerClassName = menuToggle ? 'navbar-burger is-active' : 'navbar-burger' 12 | const navbarMenuClassName = menuToggle ? 'navbar-menu is-active' : 'navbar-menu' 13 | 14 | return ( 15 | 61 | ) 62 | } 63 | 64 | export default Nav 65 | -------------------------------------------------------------------------------- /frontend/ts/routes/about.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | const AboutRoute = () => { 5 | return ( 6 |
7 |
8 |

9 | About MicroMix 10 |

11 | 12 |

13 | By default, your entire Ethereum transaction history and 14 | balances are public. All transactions can be seen on block 15 | explorers like Etherscan, and anyone 17 | who knows that you own a particular address can easily 18 | trace your payments and calculate your holdings. 19 |

20 | 21 |
22 | 23 |

24 | MicroMix helps you to reclaim your privacy. You can use it 25 | to send Ether to any address in a way that obscures your 26 | sending address. 27 |

28 | 29 |
30 | 31 |

32 | It does this using zero-knowledge proofs. You can use this 33 | app to deposit some ETH or tokens into a noncustodial smart 34 | contract, and then easily generate a proof that you had 35 | perfomed said deposit without revealing your original 36 | address. The app will then send this proof to an 37 | operator, which will submit it to the smart contract, which 38 | will in turn send the ETH or ETH to the desired recipient 39 | and reimburse the operator a small fee. 40 |

41 | 42 |
43 | 44 |

45 | If there are enough depositors, a nosy observer cannot link 46 | any particular recipient's addresses to the depositor's. 47 | To maximise the number of deposits (also known as the 48 | {' '}anonymity set), the app's user interface 49 | encourages the user to leave the app open till midnight 50 | UTC, which is when it will automatically mix the funds via 51 | the operator. 52 |

53 | 54 |
55 | 56 |

57 | This user interface makes you wait till midnight instead 58 | of telling the user how large the anonymity set is because 59 | an attacker could easily spam the contract with deposits, 60 | causing a user to falsely believe that it is large enough 61 | when it could easily be just 1, which grants no privacy at 62 | all. 63 |

64 | 65 |
66 | 67 |

68 | Additionally, since a third-party operator pays the gas for 69 | the transaction to mix the funds (also known as a 70 | withdrawal), the recipient does not need the depositor to 71 | send them gas to mix the funds (which would defeat the 72 | purpose of the mixer). 73 |

74 | 75 |
76 | 77 |

78 | Click here 81 | {' '} for the source code, build instructions, and more 82 | information about MicroMix. 83 |

84 | 85 |
86 |
87 | ) 88 | } 89 | 90 | export default AboutRoute 91 | -------------------------------------------------------------------------------- /frontend/ts/storage.ts: -------------------------------------------------------------------------------- 1 | // The functions in this file handle the storage of identities (keypair and 2 | // identityNullifier) in the browser's localStorage. The identityCommitment is 3 | // deterministically derived using mixer-crypto's genIdentityCommitment 4 | // function, so we don't store it. 5 | 6 | const config = require('../exported_config') 7 | import { 8 | Identity, 9 | EddsaPrivateKey, 10 | } from 'libsemaphore' 11 | import { Buffer } from 'buffer' 12 | 13 | interface IdentityStored { 14 | identityNullifier: BigInt, 15 | identityTrapdoor: BigInt, 16 | privKey: EddsaPrivateKey, 17 | recipientAddress: string, 18 | depositTxHash: string, 19 | withdrawTxHash: string, 20 | timestamp: number, 21 | tokenType: string, 22 | } 23 | 24 | const localStorage = window.localStorage 25 | 26 | // The storage key depends on the mixer contracts to prevent conflicts 27 | const ethMixerPrefix = config.chain.deployedAddresses.Mixer.slice(2).toLowerCase() 28 | const tokenMixerPrefix = config.chain.deployedAddresses.TokenMixer.slice(2).toLowerCase() 29 | const key = `MIXER_${ethMixerPrefix}_${tokenMixerPrefix}` 30 | 31 | const initStorage = () => { 32 | if (!localStorage.getItem(key)) { 33 | localStorage.setItem(key, JSON.stringify([])) 34 | } 35 | } 36 | 37 | const hexifyItem = (item: IdentityStored) => { 38 | return Object.assign( 39 | item, 40 | { 41 | identityNullifier: item.identityNullifier.toString(16), 42 | identityTrapdoor: item.identityTrapdoor.toString(16), 43 | privKey: item.privKey.toString('hex'), 44 | } 45 | ) 46 | } 47 | 48 | const deHexifyItem = (hexified: any): IdentityStored => { 49 | return { 50 | ...hexified, 51 | identityNullifier: BigInt('0x' + hexified.identityNullifier), 52 | identityTrapdoor: BigInt('0x' + hexified.identityTrapdoor), 53 | privKey: Buffer.from(hexified.privKey, 'hex'), 54 | } 55 | } 56 | 57 | const updateDepositTxStatus = ( 58 | identity: Identity, 59 | depositTxHash: string, 60 | ) => { 61 | let items = getRawItems() 62 | for (let i=0; i < items.length; i++) { 63 | if (items[i].identityNullifier === identity.identityNullifier.toString(16)) { 64 | items[i].depositTxHash = depositTxHash 65 | break 66 | } 67 | } 68 | saveItems(items) 69 | } 70 | 71 | const updateWithdrawTxHash = ( 72 | identity: Identity, 73 | withdrawTxHash: string, 74 | ) => { 75 | let items = getRawItems() 76 | for (let i=0; i < items.length; i++) { 77 | if (items[i].identityNullifier === identity.identityNullifier.toString(16)) { 78 | items[i].withdrawTxHash = withdrawTxHash 79 | break 80 | } 81 | } 82 | saveItems(items) 83 | } 84 | 85 | const getRawItems = () => { 86 | const stored = localStorage.getItem(key) 87 | if (!stored) { 88 | throw 'Storage not initialised' 89 | } 90 | return JSON.parse(stored) 91 | } 92 | 93 | const getItems = () => { 94 | return getRawItems().map(deHexifyItem) 95 | } 96 | 97 | const getNumItems = (): number => { 98 | return getRawItems().length 99 | } 100 | 101 | const saveItems = (items: any[]) => { 102 | const data = JSON.stringify(items.map(hexifyItem)) 103 | localStorage.setItem(key, data) 104 | } 105 | 106 | const storeDeposit = ( 107 | identity: Identity, 108 | recipientAddress: string, 109 | tokenType: string, 110 | depositTxHash=null, 111 | ) => { 112 | const items = getRawItems() 113 | items.push({ 114 | privKey: identity.keypair.privKey, 115 | identityNullifier: identity.identityNullifier, 116 | identityTrapdoor: identity.identityTrapdoor, 117 | depositTxHash, 118 | recipientAddress, 119 | tokenType, 120 | timestamp: (new Date()).getTime(), 121 | withdrawTxHash: '', 122 | }) 123 | saveItems(items) 124 | } 125 | 126 | const getNumUnwithdrawn = (): number => { 127 | return getItems().filter((item) => { 128 | return item.withdrawTxHash.length === 0 129 | }).length 130 | } 131 | 132 | const getFirstUnwithdrawn = (): IdentityStored => { 133 | const items = getItems() 134 | for (let item of items) { 135 | if (item.withdrawTxHash.length === 0) { 136 | return item 137 | } 138 | } 139 | throw new Error('All items withdrawn') 140 | } 141 | 142 | export { 143 | initStorage, 144 | storeDeposit, 145 | updateDepositTxStatus, 146 | updateWithdrawTxHash, 147 | deHexifyItem, 148 | getItems, 149 | getNumItems, 150 | getNumUnwithdrawn, 151 | getFirstUnwithdrawn, 152 | IdentityStored, 153 | } 154 | -------------------------------------------------------------------------------- /frontend/ts/utils/fetcher.ts: -------------------------------------------------------------------------------- 1 | const fetchWithoutCache = ( 2 | url: string, 3 | ) => { 4 | return fetch( 5 | url, 6 | {cache: "no-store"}, 7 | ) 8 | } 9 | 10 | export { fetchWithoutCache } 11 | -------------------------------------------------------------------------------- /frontend/ts/utils/mixAmts.ts: -------------------------------------------------------------------------------- 1 | import * as ethers from 'ethers' 2 | 3 | const config = require('../../exported_config') 4 | 5 | const mixAmtEth = config.mixAmtEth 6 | const operatorFeeEth = config.feeAmtEth.toString() 7 | const feeAmtWei = ethers.utils.parseEther(operatorFeeEth) 8 | 9 | const tokenDecimals = config.tokenDecimals 10 | const mixAmtTokens = config.mixAmtTokens 11 | const operatorFeeTokens = config.feeAmtTokens 12 | 13 | export { 14 | mixAmtEth, 15 | mixAmtTokens, 16 | operatorFeeEth, 17 | operatorFeeTokens, 18 | feeAmtWei, 19 | } 20 | -------------------------------------------------------------------------------- /frontend/ts/web3/balance.tsx: -------------------------------------------------------------------------------- 1 | import * as ethers from 'ethers' 2 | const config = require('../../exported_config') 3 | import { 4 | getTokenContract, 5 | } from './mixer' 6 | 7 | /* 8 | * Returns the current account balance in wei. 9 | * @param context The web3-react context 10 | */ 11 | const getBalance = async (context: any) => { 12 | const connector = context.connector 13 | if (connector) { 14 | const provider = new ethers.providers.Web3Provider( 15 | await connector.getProvider(config.chain.chainId), 16 | ) 17 | 18 | return await provider.getBalance(context.account) 19 | } 20 | 21 | return null 22 | } 23 | 24 | const getTokenBalance = async (context: any) => { 25 | if (context.connector) { 26 | 27 | const tokenContract = await getTokenContract(context) 28 | return await tokenContract.balanceOf(context.account) 29 | } 30 | } 31 | 32 | export { getBalance, getTokenBalance } 33 | -------------------------------------------------------------------------------- /frontend/ts/web3/deposit.tsx: -------------------------------------------------------------------------------- 1 | import * as ethers from 'ethers' 2 | const config = require('../../exported_config') 3 | import { 4 | getMixerContract, 5 | getTokenMixerContract, 6 | getTokenContract, 7 | } from './mixer' 8 | 9 | /* 10 | * Perform a web3 transaction to make a deposit 11 | * @param context The web3-react context 12 | * @param identityCommitment A hex string of the user's identity commitment 13 | * @param mixAmt The amount to mix 14 | */ 15 | const depositEth = async ( 16 | context: any, 17 | identityCommitment: string, 18 | mixAmt: ethers.utils.BigNumber, 19 | ) => { 20 | 21 | const library = context.library 22 | const connector = context.connector 23 | if (library && connector) { 24 | const provider = new ethers.providers.Web3Provider( 25 | await connector.getProvider(config.chain.chainId), 26 | ) 27 | const signer = provider.getSigner() 28 | 29 | const mixerContract = await getMixerContract(context) 30 | 31 | const tx = await mixerContract.deposit(identityCommitment, { value: mixAmt, gasLimit: 8000000 }) 32 | return tx 33 | } 34 | } 35 | 36 | const depositTokens = async( 37 | context: any, 38 | identityCommitment: string, 39 | ) => { 40 | 41 | const library = context.library 42 | const connector = context.connector 43 | if (library && connector) { 44 | const provider = new ethers.providers.Web3Provider( 45 | await connector.getProvider(config.chain.chainId), 46 | ) 47 | const signer = provider.getSigner() 48 | 49 | const mixerContract = await getTokenMixerContract(context) 50 | 51 | const tx = await mixerContract.depositERC20(identityCommitment, { gasLimit: 8000000 }) 52 | 53 | return tx 54 | } 55 | } 56 | 57 | const getTokenAllowance = async ( 58 | context: any, 59 | ) => { 60 | const library = context.library 61 | const connector = context.connector 62 | if (library && connector) { 63 | const provider = new ethers.providers.Web3Provider( 64 | await connector.getProvider(config.chain.chainId), 65 | ) 66 | const signer = provider.getSigner() 67 | 68 | const tokenContract = await getTokenContract(context) 69 | const tokenMixerAddress = config.chain.deployedAddresses.TokenMixer 70 | 71 | const tx = await tokenContract.allowance(context.account, tokenMixerAddress) 72 | return tx 73 | } 74 | } 75 | 76 | /* 77 | * Perform a web3 transaction to the ERC20 contract's approve function 78 | * @param context The web3-react context 79 | * @param numTokens The amount of tokens. This should be multiplied by 10 ^ 80 | * token.decimals before passing it to this function. 81 | */ 82 | const approveTokens = async ( 83 | context: any, 84 | numTokens: number, 85 | ) => { 86 | const library = context.library 87 | const connector = context.connector 88 | if (library && connector) { 89 | const provider = new ethers.providers.Web3Provider( 90 | await connector.getProvider(config.chain.chainId), 91 | ) 92 | const signer = provider.getSigner() 93 | 94 | const tokenContract = await getTokenContract(context) 95 | const tokenMixerAddress = config.chain.deployedAddresses.TokenMixer 96 | 97 | const tx = await tokenContract.approve(tokenMixerAddress, numTokens.toString()) 98 | 99 | return tx 100 | } 101 | } 102 | 103 | export { depositEth, depositTokens, getTokenAllowance, approveTokens } 104 | -------------------------------------------------------------------------------- /frontend/ts/web3/index.tsx: -------------------------------------------------------------------------------- 1 | import { Connectors } from 'web3-react' 2 | const { InjectedConnector } = Connectors 3 | const config = require('../../exported_config') 4 | 5 | const MetaMask = new InjectedConnector({ 6 | supportedNetworks: [config.frontend.supportedNetwork] 7 | }) 8 | 9 | export default { MetaMask } 10 | -------------------------------------------------------------------------------- /frontend/ts/web3/mixer.tsx: -------------------------------------------------------------------------------- 1 | import * as ethers from 'ethers' 2 | const mixerAbi = require('../../abis/Mixer-abi.json') 3 | const semaphoreAbi = require('../../abis/Semaphore-abi.json') 4 | const relayerRegistryAbi = require('../../abis/RelayerRegistry-abi.json') 5 | const tokenAbi = require('../../abis/ERC20-abi.json') 6 | const config = require('../../exported_config') 7 | const deployedAddresses = config.chain.deployedAddresses 8 | 9 | // It's not trivial to generalise these functions as Parcel won't let you 10 | // dynamically require JSON files 11 | 12 | const getRelayerRegistryContract = async (context) => { 13 | const provider = new ethers.providers.Web3Provider( 14 | await context.connector.getProvider(config.chain.chainId), 15 | ) 16 | const signer = provider.getSigner() 17 | 18 | return new ethers.Contract( 19 | deployedAddresses.RelayerRegistry, 20 | relayerRegistryAbi, 21 | signer, 22 | ) 23 | } 24 | 25 | const getMixerContract = async (context) => { 26 | const provider = new ethers.providers.Web3Provider( 27 | await context.connector.getProvider(config.chain.chainId), 28 | ) 29 | const signer = provider.getSigner() 30 | 31 | return new ethers.Contract( 32 | deployedAddresses.Mixer, 33 | mixerAbi, 34 | signer, 35 | ) 36 | } 37 | 38 | const getTokenMixerContract = async (context) => { 39 | const provider = new ethers.providers.Web3Provider( 40 | await context.connector.getProvider(config.chain.chainId), 41 | ) 42 | const signer = provider.getSigner() 43 | 44 | return new ethers.Contract( 45 | deployedAddresses.TokenMixer, 46 | mixerAbi, 47 | signer, 48 | ) 49 | } 50 | 51 | const getSemaphoreContract = async (context) => { 52 | const provider = new ethers.providers.Web3Provider( 53 | await context.connector.getProvider(config.chain.chainId), 54 | ) 55 | const signer = provider.getSigner() 56 | 57 | return new ethers.Contract( 58 | deployedAddresses.Semaphore, 59 | semaphoreAbi, 60 | signer, 61 | ) 62 | } 63 | 64 | const getTokenSemaphoreContract = async (context) => { 65 | const provider = new ethers.providers.Web3Provider( 66 | await context.connector.getProvider(config.chain.chainId), 67 | ) 68 | const signer = provider.getSigner() 69 | 70 | return new ethers.Contract( 71 | deployedAddresses.TokenSemaphore, 72 | semaphoreAbi, 73 | signer, 74 | ) 75 | } 76 | 77 | const getTokenContract = async (context) => { 78 | const provider = new ethers.providers.Web3Provider( 79 | await context.connector.getProvider(config.chain.chainId), 80 | ) 81 | const signer = provider.getSigner() 82 | 83 | return new ethers.Contract( 84 | deployedAddresses.Token, 85 | tokenAbi, 86 | signer, 87 | ) 88 | } 89 | 90 | export { 91 | getRelayerRegistryContract, 92 | getMixerContract, 93 | getSemaphoreContract, 94 | getTokenMixerContract, 95 | getTokenSemaphoreContract, 96 | getTokenContract, 97 | } 98 | -------------------------------------------------------------------------------- /frontend/ts/web3/quickWithdraw.tsx: -------------------------------------------------------------------------------- 1 | import * as ethers from 'ethers' 2 | import { getRelayerRegistryContract, getMixerContract, getTokenMixerContract } from './mixer' 3 | const config = require('../../exported_config') 4 | const deployedAddresses = config.chain.deployedAddresses 5 | 6 | const genDepositProof = ( 7 | signal, 8 | proof, 9 | publicSignals, 10 | recipientAddress, 11 | fee, 12 | ) => { 13 | return { 14 | signal, 15 | a: [ proof.pi_a[0].toString(), proof.pi_a[1].toString() ], 16 | b: [ 17 | [ proof.pi_b[0][1].toString(), proof.pi_b[0][0].toString() ], 18 | [ proof.pi_b[1][1].toString(), proof.pi_b[1][0].toString() ], 19 | ], 20 | c: [ proof.pi_c[0].toString(), proof.pi_c[1].toString() ], 21 | input: publicSignals.map((x) => x.toString()), 22 | recipientAddress, 23 | fee, 24 | } 25 | } 26 | 27 | /* 28 | * Perform a web3 transaction to make quick withdrawal of ETH 29 | * @param context The web3-react context 30 | * @param identityCommitment A hex string of the user's identity commitment 31 | */ 32 | const quickWithdrawEth = async ( 33 | context: any, 34 | signal, 35 | proof, 36 | publicSignals, 37 | recipientAddress, 38 | feeAmt, 39 | broadcasterAddress, 40 | ) => { 41 | const library = context.library 42 | const connector = context.connector 43 | if (library && connector) { 44 | const provider = new ethers.providers.Web3Provider( 45 | await connector.getProvider(config.chain.chainId), 46 | ) 47 | const signer = provider.getSigner() 48 | const mixerContract = await getMixerContract(context) 49 | const relayerRegistryContract = await getRelayerRegistryContract(context) 50 | 51 | const depositProof = genDepositProof( 52 | signal, 53 | proof, 54 | publicSignals, 55 | recipientAddress, 56 | feeAmt, 57 | ) 58 | 59 | const iface = new ethers.utils.Interface(mixerContract.interface.abi) 60 | const callData = iface.functions.mix.encode([depositProof, broadcasterAddress]) 61 | 62 | return relayerRegistryContract.relayCall( 63 | deployedAddresses.Mixer, 64 | callData, 65 | { gasLimit: 8000000 }, 66 | ) 67 | } 68 | } 69 | 70 | /* 71 | * Perform a web3 transaction to make quick withdrawal of tokens 72 | * @param context The web3-react context 73 | * @param identityCommitment A hex string of the user's identity commitment 74 | */ 75 | const quickWithdrawTokens = async ( 76 | context: any, 77 | signal, 78 | proof, 79 | publicSignals, 80 | recipientAddress, 81 | feeAmt, 82 | broadcasterAddress, 83 | ) => { 84 | const library = context.library 85 | const connector = context.connector 86 | if (library && connector) { 87 | const provider = new ethers.providers.Web3Provider( 88 | await connector.getProvider(config.chain.chainId), 89 | ) 90 | const signer = provider.getSigner() 91 | const mixerContract = await getTokenMixerContract(context) 92 | const relayerRegistryContract = await getRelayerRegistryContract(context) 93 | 94 | const depositProof = genDepositProof( 95 | signal, 96 | proof, 97 | publicSignals, 98 | recipientAddress, 99 | feeAmt, 100 | ) 101 | 102 | const iface = new ethers.utils.Interface(mixerContract.interface.abi) 103 | const callData = iface.functions.mixERC20.encode([depositProof, broadcasterAddress]) 104 | 105 | return relayerRegistryContract.relayCall( 106 | deployedAddresses.TokenMixer, 107 | callData, 108 | { gasLimit: 8000000 }, 109 | ) 110 | } 111 | } 112 | 113 | export { quickWithdrawEth, quickWithdrawTokens } 114 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "esModuleInterop": true, 6 | "outDir": "./build", 7 | "jsx": "react", 8 | "lib": [ "es2015", "dom" ] 9 | }, 10 | "include": [ 11 | "./ts" 12 | ], 13 | "exclude": [ 14 | "*/dist/*", 15 | "*/build/*", 16 | "node_modules/*" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const HtmlWebpackPlugin = require('html-webpack-plugin') 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | 5 | const isProduction = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'kovan' 6 | 7 | module.exports = { 8 | mode: isProduction ? 'production' : 'development', 9 | entry: { 10 | index: './build/index.js', 11 | }, 12 | devtool: "source-map", 13 | output: { 14 | filename: '[name].[contenthash].js', 15 | }, 16 | optimization: { 17 | minimize: isProduction, 18 | minimizer: [ 19 | new TerserPlugin({ 20 | terserOptions: { 21 | compress: true, 22 | mangle: false, 23 | }, 24 | }), 25 | ], 26 | }, 27 | plugins: [ 28 | new HtmlWebpackPlugin({ 29 | title: 'MicroMix', 30 | template: 'index.html' 31 | }) 32 | ], 33 | externals: /^(worker_threads)$/, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.(png|svg|jpg|gif)$/, 38 | use: ['file-loader'], 39 | }, 40 | { 41 | test: /\.less$/, 42 | include: [ 43 | path.resolve(__dirname, "less/") 44 | ], 45 | use: [ 46 | 'style-loader', 47 | 'css-loader', 48 | 'less-loader', 49 | ] 50 | } 51 | ], 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "frontend", 4 | "backend", 5 | "contracts", 6 | "utils", 7 | "config" 8 | ], 9 | "version": "0.0.1", 10 | "npmClient": "npm" 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "scripts": { 5 | "bootstrap": "lerna bootstrap", 6 | "build": "lerna run build", 7 | "buildBaseImage": "./scripts/buildBaseImage.sh", 8 | "buildImages": "./scripts/buildImages.sh" 9 | }, 10 | "devDependencies": { 11 | "lerna": "3.15.0", 12 | "typescript": "^3.5.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /scripts/buildImages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Building mixer-build" 5 | docker build -f Dockerfile -t mixer-build --target mixer-build --build-arg NODE_ENV=$NODE_ENV . 6 | 7 | echo "Building mixer-base" 8 | docker build -f Dockerfile -t mixer-base --target mixer-base --build-arg NODE_ENV=$NODE_ENV . 9 | 10 | echo "Building images using docker-compose" 11 | docker-compose -f docker/docker-compose.yml build 12 | -------------------------------------------------------------------------------- /scripts/downloadSnarks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERIFIER_SOL="https://micromixtest.blob.core.windows.net/snarks/verifier.sol" 4 | VERIFICATION_KEY_JSON="https://micromixtest.blob.core.windows.net/snarks/verification_key.json" 5 | PROVING_KEY_BIN="https://micromixtest.blob.core.windows.net/snarks/proving_key.bin" 6 | CIRCUIT_JSON="https://micromixtest.blob.core.windows.net/snarks/circuit.json" 7 | 8 | CIRCUIT_JSON_PATH="semaphore/semaphorejs/build/circuit.json" 9 | PROVING_KEY_BIN_PATH="semaphore/semaphorejs/build/proving_key.bin" 10 | VERIFIER_SOL_PATH="semaphore/semaphorejs/build/verifier.sol" 11 | VERIFICATION_KEY_PATH="semaphore/semaphorejs/build/verification_key.json" 12 | 13 | mkdir -p semaphore/semaphorejs/build 14 | 15 | if [ "$1" = "--only-verifier" ]; then 16 | echo "Downloading verifier.sol" 17 | wget --quiet -nc $VERIFIER_SOL -O $VERIFIER_SOL_PATH 18 | echo "Downloading verification_key.json" 19 | wget --quiet -nc $VERIFICATION_KEY_JSON -O $VERIFICATION_KEY_PATH 20 | 21 | else 22 | if [ ! -f "$CIRCUIT_JSON_PATH" ]; then 23 | echo "Downloading circuit.json" 24 | wget --quiet -O - $CIRCUIT_JSON | gunzip -c > $CIRCUIT_JSON_PATH 25 | fi 26 | 27 | if [ ! -f "$PROVING_KEY_BIN_PATH" ]; then 28 | echo "Downloading proving_key.bin" 29 | wget --quiet -O - $PROVING_KEY_BIN | gunzip -c > $PROVING_KEY_BIN_PATH 30 | fi 31 | 32 | #echo "Downloading proving_key.json" 33 | #wget --quiet -nc $PROVING_KEY_JSON -O $PROVING_KEY_JSON_PATH 34 | 35 | echo "Downloading verification_key.json" 36 | wget --quiet -nc $VERIFICATION_KEY_JSON -O $VERIFICATION_KEY_PATH 37 | 38 | echo "Downloading verifier.sol" 39 | wget --quiet -nc $VERIFIER_SOL -O $VERIFIER_SOL_PATH 40 | fi 41 | 42 | -------------------------------------------------------------------------------- /scripts/runImages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose -f docker/docker-compose.yml up -d 4 | -------------------------------------------------------------------------------- /scripts/serveSnarks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cd "$(dirname "$0")" 4 | cd ../semaphore/semaphorejs 5 | http-server -p 8000 --cors 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "alwaysStrict": true, 5 | "allowJs": true, 6 | "noImplicitAny": false, 7 | "forceConsistentCasingInFileNames": true, 8 | "noUnusedLocals": false, 9 | "noUnusedParameters": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "sourceMap": true, 13 | "strict": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/tslint-microsoft-contrib"], 3 | "files": "./js/", 4 | "extends": [ 5 | "tslint-microsoft-contrib" 6 | ], 7 | "rules": { 8 | "align": false, 9 | "export-name": false, 10 | "forin": false, 11 | "function-name": false, 12 | "import-name": false, 13 | "indent": [true, "spaces", 4], 14 | "interface-name": false, 15 | "insecure-random": false, 16 | "max-func-body-length": false, 17 | "max-line-length": false, 18 | "member-ordering": false, 19 | "missing-jsdoc": false, 20 | "mocha-no-side-effect-code": false, 21 | "mocha-unneeded-done": false, 22 | "newline-per-chained-call": false, 23 | "no-any": false, 24 | "no-console": false, 25 | "no-default-export": false, 26 | "no-empty": false, 27 | "no-for-in": false, 28 | "no-function-constructor-with-string-args": false, 29 | "no-increment-decrement": false, 30 | "no-invalid-template-strings": false, 31 | "no-parameter-properties": false, 32 | "no-parameter-reassignment": false, 33 | "no-non-null-assertion": false, 34 | "no-null-keyword": false, 35 | "no-redundant-jsdoc": false, 36 | "no-relative-imports": false, 37 | "no-require-imports": false, 38 | "no-string-literal": false, 39 | "no-unused-locals": false, 40 | "no-unused-parameters": false, 41 | "no-unsafe-any": false, 42 | "no-submodule-imports": false, 43 | "no-suspicious-comment": false, 44 | "no-trailing-whitespace": [true, "ignore-blank-lines"], 45 | "no-unnecessary-class": false, 46 | "no-unnecessary-override": false, 47 | "no-var-requires": false, 48 | "no-void-expression": false, 49 | "non-literal-require": false, 50 | "one-line": false, 51 | "ordered-imports": false, 52 | "prefer-template": false, 53 | "prefer-type-cast": false, 54 | "promise-function-async": false, 55 | "react-this-binding-issue": false, 56 | "semicolon": [true, "never"], 57 | "trailing-comma": [ // https://palantir.github.io/tslint/rules/trailing-comma/ 58 | true, 59 | { 60 | "multiline": "always", 61 | "singleline": "never" 62 | } 63 | ], 64 | "typedef": [ 65 | true, 66 | "parameter", 67 | "arrow-parameter", 68 | "property-declaration", 69 | "member-variable-declaration" 70 | ], 71 | "variable-name": false 72 | }, 73 | "linterOptions": { 74 | "exclude": [ 75 | "node_modules/**" 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /utils/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mixer-utils", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "12.7.2", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", 10 | "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", 11 | "dev": true 12 | }, 13 | "assert-plus": { 14 | "version": "1.0.0", 15 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 16 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 17 | }, 18 | "core-util-is": { 19 | "version": "1.0.2", 20 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 21 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 22 | }, 23 | "extsprintf": { 24 | "version": "1.4.0", 25 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz", 26 | "integrity": "sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=" 27 | }, 28 | "inherits": { 29 | "version": "2.0.3", 30 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 31 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 32 | }, 33 | "path": { 34 | "version": "0.12.7", 35 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", 36 | "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", 37 | "requires": { 38 | "process": "^0.11.1", 39 | "util": "^0.10.3" 40 | } 41 | }, 42 | "process": { 43 | "version": "0.11.10", 44 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 45 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" 46 | }, 47 | "util": { 48 | "version": "0.10.4", 49 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", 50 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", 51 | "requires": { 52 | "inherits": "2.0.3" 53 | } 54 | }, 55 | "verror": { 56 | "version": "1.10.0", 57 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 58 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 59 | "requires": { 60 | "assert-plus": "^1.0.0", 61 | "core-util-is": "1.0.2", 62 | "extsprintf": "^1.2.0" 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mixer-utils", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "watch": "tsc --watch" 9 | }, 10 | "author": "Koh Wei Jie", 11 | "license": "GPL-3.0-or-later", 12 | "dependencies": { 13 | "path": "^0.12.7", 14 | "verror": "^1.10.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^12.0.10" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /utils/ts/index.ts: -------------------------------------------------------------------------------- 1 | const sleep = (ms: number): Promise => { 2 | return new Promise((resolve: Function) => setTimeout(resolve, ms)) 3 | } 4 | 5 | const hexify = (n: BigInt) => { 6 | return '0x' + n.toString(16) 7 | } 8 | 9 | const genMixParams = ( 10 | signal: string, 11 | proof: any, 12 | recipientAddress: string, 13 | fee: BigInt, 14 | publicSignals: BigInt[], 15 | ) => { 16 | return { 17 | signal, 18 | a: proof.pi_a.slice(0, 2).map(hexify), 19 | b: [ 20 | [ 21 | hexify(proof.pi_b[0][1]), 22 | hexify(proof.pi_b[0][0]), 23 | ], 24 | [ 25 | hexify(proof.pi_b[1][1]), 26 | hexify(proof.pi_b[1][0]), 27 | ], 28 | ], 29 | c: proof.pi_c.slice(0, 2).map(hexify), 30 | input: publicSignals.map(hexify), 31 | recipientAddress, 32 | fee: hexify(fee), 33 | } 34 | } 35 | 36 | export { sleep, genMixParams } 37 | -------------------------------------------------------------------------------- /utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./build" 5 | }, 6 | "include": [ 7 | "./ts" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------