Compare commits
50 Commits
Author | SHA1 | Date |
---|---|---|
Alexey Pertsev | 1ef6a263ac | |
Drygin | f9f19b70e4 | |
Drygin | 10aeb05417 | |
Roman Semenov | 896fc224ff | |
HowJMay | 0b8bbf6317 | |
poma | 7d924e2447 | |
poma | 2ec693d0de | |
poma | 0cf811b854 | |
poma | f468de9a0a | |
Roman Semenov | 94dfad9cd2 | |
poma | 23543683f3 | |
poma | 0c6e638852 | |
poma | 801f29a4b7 | |
mirru2532 | 5cb9d60178 | |
Alexey | 3603b1c9e1 | |
Alexey | c12643e2c2 | |
poma | 54a7bdcb04 | |
poma | f189a657c9 | |
poma | 3ad634594e | |
Alexey | 127a61e21f | |
Alexey | c559a79396 | |
Alexey | 78bd4175fa | |
Alexey | 4069b61421 | |
poma | f5d8f6d971 | |
poma | 8580c5e427 | |
poma | a359e86f85 | |
poma | 346ffcee3c | |
poma | c6b442713a | |
Roman Storm | 3c4def1e64 | |
Roman Semenov | 77af0c5bdd | |
Lucien Nocelli | 4f0a23426f | |
poma | b438f8db7b | |
Alexey Pertsev | d02ff4faa2 | |
Roman Storm | 1ad2158af1 | |
Roman Storm | 18e6a19800 | |
poma | 17308c9670 | |
Roman Storm | 5c3648cedc | |
Roman Storm | a0ef1a526d | |
Roman Semenov | fddd79d9bd | |
Alexey | 09baf9761d | |
Alexey | ea1435b115 | |
Alexey | 6d383235bb | |
Alexey | f48861a4f2 | |
Tsunami | 4408f39b5a | |
Alexey | c7d912c2e7 | |
Alexey | 4e120f26cb | |
Alexey | f90a898001 | |
Alexey | 03175a2277 | |
g. nicholas d'andrea | 7ceebf48d5 | |
Alexey | 55e50fee3e |
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "plugin:prettier/recommended", "prettier"],
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"indent": ["error", 2],
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
"quotes": ["error", "single"],
|
||||||
|
"semi": ["error", "never"],
|
||||||
|
"object-curly-spacing": ["error", "always"],
|
||||||
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
|
"require-await": "error",
|
||||||
|
"prettier/prettier": ["error", { "printWidth": 110 }]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"node": true,
|
|
||||||
"browser": true,
|
|
||||||
"es6": true,
|
|
||||||
"mocha": true
|
|
||||||
},
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"globals": {
|
|
||||||
"Atomics": "readonly",
|
|
||||||
"SharedArrayBuffer": "readonly"
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2018
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
2
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"single"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"object-curly-spacing": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"require-await": "error"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['*']
|
||||||
|
tags: ['v[0-9]+.[0-9]+.[0-9]+']
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12
|
||||||
|
- run: yarn install
|
||||||
|
- run: yarn download
|
||||||
|
- run: cp .env.example .env
|
||||||
|
- run: npx ganache-cli > /dev/null &
|
||||||
|
- run: npm run migrate:dev
|
||||||
|
- run: yarn test
|
||||||
|
- run: node src/cli.js test
|
||||||
|
- run: yarn lint
|
||||||
|
- run: yarn coverage
|
||||||
|
- name: Coveralls
|
||||||
|
uses: coverallsapp/github-action@master
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Telegram Failure Notification
|
||||||
|
uses: appleboy/telegram-action@0.0.7
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
message: ❗ Build failed for [${{ github.repository }}](https://github.com/${{ github.repository }}/actions) because of ${{ github.actor }}
|
||||||
|
format: markdown
|
||||||
|
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
|
@ -96,3 +96,5 @@ typings/
|
||||||
|
|
||||||
ERC20Tornado_flat.sol
|
ERC20Tornado_flat.sol
|
||||||
ETHTornado_flat.sol
|
ETHTornado_flat.sol
|
||||||
|
|
||||||
|
coverage.json
|
|
@ -0,0 +1,9 @@
|
||||||
|
.vscode
|
||||||
|
build
|
||||||
|
circuits
|
||||||
|
contracts/Verifier.sol
|
||||||
|
scripts/ganacheHelper.js
|
||||||
|
cli.js
|
||||||
|
index.js
|
||||||
|
coverage
|
||||||
|
coverage.json
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"semi": false,
|
||||||
|
"printWidth": 110,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.sol",
|
||||||
|
"options": {
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 130
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,6 +1,13 @@
|
||||||
{
|
{
|
||||||
"extends": "solhint:recommended",
|
"extends": "solhint:recommended",
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": ["error", 2]
|
"prettier/prettier": [
|
||||||
}
|
"error",
|
||||||
|
{
|
||||||
|
"printWidth": 110
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quotes": ["error", "double"]
|
||||||
|
},
|
||||||
|
"plugins": ["prettier"]
|
||||||
}
|
}
|
||||||
|
|
14
.travis.yml
14
.travis.yml
|
@ -1,14 +0,0 @@
|
||||||
dist: trusty
|
|
||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- "11"
|
|
||||||
install:
|
|
||||||
- npm ci
|
|
||||||
- cp .env.example .env
|
|
||||||
- travis_wait 30 npm run build
|
|
||||||
- npx ganache-cli > /dev/null &
|
|
||||||
- npm run migrate:dev
|
|
||||||
script:
|
|
||||||
- npm run test
|
|
||||||
- npm run eslint
|
|
||||||
- node cli.js test
|
|
107
README.md
107
README.md
|
@ -1,44 +1,47 @@
|
||||||
# Tornado Cash Privacy Solution [![Build Status](https://travis-ci.org/tornadocash/tornado-core.svg?branch=master)](https://travis-ci.org/tornadocash/tornado-core)
|
# Tornado Cash Privacy Solution [![build status](https://github.com/tornadocash/tornado-core/actions/workflows/build.yml/badge.svg)](https://github.com/tornadocash/tornado-core/actions/workflows/build.yml) [![Coverage Status](https://coveralls.io/repos/github/tornadocash/tornado-core/badge.svg?branch=master)](https://coveralls.io/github/tornadocash/tornado-core?branch=master)
|
||||||
|
|
||||||
Tornado Cash is a non-custodial Ethereum and ERC20 privacy solution based on zkSNARKs. It improves transaction privacy by breaking the on-chain link between recipient and destination addresses. It uses a smart contract that accepts ETH deposits that can be withdrawn by a different address. Whenever ETH is withdrawn by the new address, there is no way to link the withdrawal to the deposit, ensuring complete privacy.
|
Tornado Cash is a non-custodial Ethereum and ERC20 privacy solution based on zkSNARKs. It improves transaction privacy by breaking the on-chain link between the recipient and destination addresses. It uses a smart contract that accepts ETH deposits that can be withdrawn by a different address. Whenever ETH is withdrawn by the new address, there is no way to link the withdrawal to the deposit, ensuring complete privacy.
|
||||||
|
|
||||||
To make a deposit user generates a secret and sends its hash (called a commitment) along with the deposit amount to the Tornado smart contract. The contract accepts the deposit and adds the commitment to its list of deposits.
|
To make a deposit user generates a secret and sends its hash (called a commitment) along with the deposit amount to the Tornado smart contract. The contract accepts the deposit and adds the commitment to its list of deposits.
|
||||||
|
|
||||||
Later, the user decides to make a withdrawal. In order to do that, the user should provide a proof that he or she possesses a secret to an unspent commitment from the smart contract’s list of deposits. zkSnark technology allows that to happen without revealing which exact deposit corresponds to this secret. The smart contract will check the proof, and transfer deposited funds to the address specified for withdrawal. An external observer will be unable to determine which deposit this withdrawal came from.
|
Later, the user decides to make a withdrawal. To do that, the user should provide a proof that he or she possesses a secret to an unspent commitment from the smart contract’s list of deposits. zkSnark technology allows that to happen without revealing which exact deposit corresponds to this secret. The smart contract will check the proof and transfer deposited funds to the address specified for withdrawal. An external observer will be unable to determine which deposit this withdrawal came from.
|
||||||
|
|
||||||
You can read more about it in [this medium article](https://medium.com/@tornado.cash/introducing-private-transactions-on-ethereum-now-42ee915babe0)
|
You can read more about it in [this Medium article](https://medium.com/@tornado.cash/introducing-private-transactions-on-ethereum-now-42ee915babe0)
|
||||||
|
|
||||||
## Specs
|
## Specs
|
||||||
- Deposit gas const: 1088354 (43381 + 50859 * tree_depth)
|
|
||||||
|
- Deposit gas cost: 1088354 (43381 + 50859 \* tree_depth)
|
||||||
- Withdraw gas cost: 301233
|
- Withdraw gas cost: 301233
|
||||||
- Circuit Constraints = 28271 (1869 + 1325 * tree_depth)
|
- Circuit Constraints = 28271 (1869 + 1325 \* tree_depth)
|
||||||
- Circuit Proof time = 10213ms (1071 + 347 * tree_depth)
|
- Circuit Proof time = 10213ms (1071 + 347 \* tree_depth)
|
||||||
- Serverless
|
- Serverless
|
||||||
|
|
||||||
![image](diagram.png)
|
![image](docs/diagram.png)
|
||||||
|
|
||||||
## Whitepaper
|
## Whitepaper
|
||||||
**[https://tornado.cash/Tornado.cash_whitepaper_v1.4.pdf](https://tornado.cash/Tornado.cash_whitepaper_v1.4.pdf)**
|
|
||||||
|
**[TornadoCash_whitepaper_v1.4.pdf](https://tornado.cash/audits/TornadoCash_whitepaper_v1.4.pdf)**
|
||||||
|
|
||||||
## Was it audited?
|
## Was it audited?
|
||||||
|
|
||||||
Tornado.cash protocols, circuits, and smart contracts were audited by a group of experts from [ABDK Consulting](https://www.abdk.consulting), specializing in zero knowledge, cryptography, and smart contracts.
|
Tornado.cash protocols, circuits, and smart contracts were audited by a group of experts from [ABDK Consulting](https://www.abdk.consulting), specializing in zero-knowledge, cryptography, and smart contracts.
|
||||||
|
|
||||||
During the audit no critical issues were found and all outstanding issues were fixed. The results can be found here:
|
During the audit, no critical issues were found and all outstanding issues were fixed. The results can be found here:
|
||||||
|
|
||||||
* Cryptographic review https://tornado.cash/Tornado_cryptographic_review.pdf
|
- Cryptographic review https://tornado.cash/audits/TornadoCash_cryptographic_review_ABDK.pdf
|
||||||
* Smart contract audit https://tornado.cash/Tornado_solidity_audit.pdf
|
- Smart contract audit https://tornado.cash/audits/TornadoCash_contract_audit_ABDK.pdf
|
||||||
* Zk-SNARK circuits audit https://tornado.cash/Tornado_circuit_audit.pdf
|
- Zk-SNARK circuits audit https://tornado.cash/audits/TornadoCash_circuit_audit_ABDK.pdf
|
||||||
|
|
||||||
Underlying circomlib dependency is currently being audited, and the team already published most of the fixes for found issues
|
Underlying circomlib dependency is currently being audited, and the team already published most of the fixes for found issues
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
1. `node v11.15.0`
|
1. `node v11.15.0`
|
||||||
2. `npm install -g npx`
|
2. `npm install -g npx`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
You can see example usage in cli.js, it works both in console and in browser.
|
You can see example usage in cli.js, it works both in the console and in the browser.
|
||||||
|
|
||||||
1. `npm install`
|
1. `npm install`
|
||||||
1. `cp .env.example .env`
|
1. `cp .env.example .env`
|
||||||
|
@ -53,13 +56,16 @@ Use browser version on Kovan:
|
||||||
1. `npx http-server` - serve current dir, you can use any other static http server
|
1. `npx http-server` - serve current dir, you can use any other static http server
|
||||||
1. Open `localhost:8080`
|
1. Open `localhost:8080`
|
||||||
|
|
||||||
Use with command line version. Works for Ganache, Kovan and Mainnet:
|
Use the command-line version. Works for Ganache, Kovan, and Mainnet:
|
||||||
|
|
||||||
### Initialization
|
### Initialization
|
||||||
|
|
||||||
1. `cp .env.example .env`
|
1. `cp .env.example .env`
|
||||||
1. `npm run download`
|
1. `npm run download`
|
||||||
1. `npm run build:contract`
|
1. `npm run build:contract`
|
||||||
|
|
||||||
### Ganache
|
### Ganache
|
||||||
|
|
||||||
1. make sure you complete steps from Initialization
|
1. make sure you complete steps from Initialization
|
||||||
1. `ganache-cli -i 1337`
|
1. `ganache-cli -i 1337`
|
||||||
1. `npm run migrate:dev`
|
1. `npm run migrate:dev`
|
||||||
|
@ -67,14 +73,16 @@ Use with command line version. Works for Ganache, Kovan and Mainnet:
|
||||||
1. `./cli.js --help`
|
1. `./cli.js --help`
|
||||||
|
|
||||||
### Kovan, Mainnet
|
### Kovan, Mainnet
|
||||||
1. make sure you complete steps from Initialization
|
|
||||||
1. Add `PRIVATE_KEY` to `.env` file
|
1. Please use https://github.com/tornadocash/tornado-cli
|
||||||
1. `./cli.js --help`
|
Reason: because tornado-core uses websnark `2041cfa5fa0b71cd5cca9022a4eeea4afe28c9f7` commit hash in order to work with local trusted setup. Tornado-cli uses `4c0af6a8b65aabea3c09f377f63c44e7a58afa6d` commit with production trusted setup of tornadoCash
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./cli.js deposit ETH 0.1 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448
|
./cli.js deposit ETH 0.1 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448
|
||||||
```
|
```
|
||||||
|
|
||||||
> Your note: tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
|
> Your note: tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
|
||||||
> Tornado ETH balance is 8.9
|
> Tornado ETH balance is 8.9
|
||||||
> Sender account ETH balance is 1004873.470619891361352542
|
> Sender account ETH balance is 1004873.470619891361352542
|
||||||
|
@ -86,39 +94,86 @@ Example:
|
||||||
./cli.js withdraw tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448 --relayer https://kovan-frelay.duckdns.org
|
./cli.js withdraw tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448 --relayer https://kovan-frelay.duckdns.org
|
||||||
```
|
```
|
||||||
|
|
||||||
> Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
|
> Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
|
||||||
> Getting current state from tornado contract
|
> Getting current state from tornado contract
|
||||||
> Generating SNARK proof
|
> Generating SNARK proof
|
||||||
> Proof time: 9117.051ms
|
> Proof time: 9117.051ms
|
||||||
> Sending withdraw transaction through relay
|
> Sending withdraw transaction through the relay
|
||||||
> Transaction submitted through the relay. View transaction on etherscan https://kovan.etherscan.io/tx/0xcb21ae8cad723818c6bc7273e83e00c8393fcdbe74802ce5d562acad691a2a7b
|
> Transaction submitted through the relay. View transaction on etherscan https://kovan.etherscan.io/tx/0xcb21ae8cad723818c6bc7273e83e00c8393fcdbe74802ce5d562acad691a2a7b
|
||||||
> Transaction mined in block 17036120
|
> Transaction mined in block 17036120
|
||||||
> Done
|
> Done
|
||||||
|
|
||||||
|
|
||||||
## Deploy ETH Tornado Cash
|
## Deploy ETH Tornado Cash
|
||||||
|
|
||||||
1. `cp .env.example .env`
|
1. `cp .env.example .env`
|
||||||
1. Tune all necessary params
|
1. Tune all necessary params
|
||||||
1. `npx truffle migrate --network kovan --reset --f 2 --to 4`
|
1. `npx truffle migrate --network kovan --reset --f 2 --to 4`
|
||||||
|
|
||||||
## Deploy ERC20 Tornado Cash
|
## Deploy ERC20 Tornado Cash
|
||||||
|
|
||||||
1. `cp .env.example .env`
|
1. `cp .env.example .env`
|
||||||
1. Tune all necessary params
|
1. Tune all necessary params
|
||||||
1. `npx truffle migrate --network kovan --reset --f 2 --to 3`
|
1. `npx truffle migrate --network kovan --reset --f 2 --to 3`
|
||||||
1. `npx truffle migrate --network kovan --reset --f 5`
|
1. `npx truffle migrate --network kovan --reset --f 5`
|
||||||
|
|
||||||
**Note**. If you want to reuse the same verifier for all the instances, then after you deployed one of the instances you should only run 4th or 5th migration for ETH or ERC20 contracts respectively (`--f 4 --to 4` or `--f 5`).
|
**Note**. If you want to reuse the same verifier for all the instances, then after you deployed one of the instances you should only run the 4th or 5th migration for ETH or ERC20 contracts respectively (`--f 4 --to 4` or `--f 5`).
|
||||||
|
|
||||||
|
## How to resolve ENS name to DNS name for a relayer
|
||||||
|
|
||||||
|
1. Visit https://etherscan.io/enslookup and put relayer ENS name to the form.
|
||||||
|
2. Copy the namehash (1) and click on the `Resolver` link (2)
|
||||||
|
![enslookup](docs/enslookup.png)
|
||||||
|
3. Go to the `Contract` tab. Click on `Read Contract` and scroll down to the `5. text` method.
|
||||||
|
4. Put the values:
|
||||||
|
![resolver](docs/resolver.png)
|
||||||
|
5. Click `Query` and you will get the DNS name. Just add `https://` to it and use it as `relayer url`
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
Special thanks to @barryWhiteHat and @kobigurk for valuable input,
|
Special thanks to @barryWhiteHat and @kobigurk for valuable input,
|
||||||
and to @jbaylina for awesome [Circom](https://github.com/iden3/circom) & [Websnark](https://github.com/iden3/websnark) framework
|
and @jbaylina for awesome [Circom](https://github.com/iden3/circom) & [Websnark](https://github.com/iden3/websnark) framework
|
||||||
|
|
||||||
## Minimal demo example
|
## Minimal demo example
|
||||||
|
|
||||||
1. `npm i`
|
1. `npm i`
|
||||||
1. `ganache-cli -d`
|
1. `ganache-cli -d`
|
||||||
1. `npm run downloadKeys`
|
1. `npm run download`
|
||||||
1. `npm build:contract`
|
1. `npm run build:contract`
|
||||||
1. `cp .env.example .env`
|
1. `cp .env.example .env`
|
||||||
1. `npm run migrate:dev`
|
1. `npm run migrate:dev`
|
||||||
1. `node minimal-demo.js`
|
1. `node minimal-demo.js`
|
||||||
|
|
||||||
|
## Run tests/coverage
|
||||||
|
|
||||||
|
Prepare test environment:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn install
|
||||||
|
yarn download
|
||||||
|
cp .env.example .env
|
||||||
|
npx ganache-cli > /dev/null &
|
||||||
|
npm run migrate:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
Run coverage:
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Emulate MPC trusted setup ceremony
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install zkutil
|
||||||
|
npx circom circuits/withdraw.circom -o build/circuits/withdraw.json
|
||||||
|
zkutil setup -c build/circuits/withdraw.json -p build/circuits/withdraw.params
|
||||||
|
zkutil export-keys -c build/circuits/withdraw.json -p build/circuits/withdraw.params -r build/circuits/withdraw_proving_key.json -v build/circuits/withdraw_verification_key.json
|
||||||
|
zkutil generate-verifier -p build/circuits/withdraw.params -v build/circuits/Verifier.sol
|
||||||
|
sed -i -e 's/pragma solidity \^0.6.0/pragma solidity 0.5.17/g' ./build/circuits/Verifier.sol
|
||||||
|
```
|
||||||
|
|
140
config.js
140
config.js
|
@ -1,140 +0,0 @@
|
||||||
require('dotenv').config()
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
deployments: {
|
|
||||||
netId1: {
|
|
||||||
eth: {
|
|
||||||
instanceAddress: {
|
|
||||||
'0.1': '0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc',
|
|
||||||
'1': '0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936',
|
|
||||||
'10': '0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF',
|
|
||||||
'100': '0xA160cdAB225685dA1d56aa342Ad8841c3b53f291'
|
|
||||||
},
|
|
||||||
symbol: 'ETH',
|
|
||||||
decimals: 18
|
|
||||||
},
|
|
||||||
dai: {
|
|
||||||
instanceAddress: {
|
|
||||||
'100': '0xD4B88Df4D29F5CedD6857912842cff3b20C8Cfa3',
|
|
||||||
'1000': '0xFD8610d20aA15b7B2E3Be39B396a1bC3516c7144',
|
|
||||||
'10000': '0xF60dD140cFf0706bAE9Cd734Ac3ae76AD9eBC32A',
|
|
||||||
'100000': undefined
|
|
||||||
},
|
|
||||||
tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
|
||||||
symbol: 'DAI',
|
|
||||||
decimals: 18
|
|
||||||
},
|
|
||||||
cdai: {
|
|
||||||
instanceAddress: {
|
|
||||||
'5000': '0x22aaA7720ddd5388A3c0A3333430953C68f1849b',
|
|
||||||
'50000': '0xBA214C1c1928a32Bffe790263E38B4Af9bFCD659',
|
|
||||||
'500000': '0xb1C8094B234DcE6e03f10a5b673c1d8C69739A00',
|
|
||||||
'5000000': undefined
|
|
||||||
},
|
|
||||||
tokenAddress: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643',
|
|
||||||
symbol: 'cDAI',
|
|
||||||
decimals: 8
|
|
||||||
},
|
|
||||||
usdc: {
|
|
||||||
instanceAddress: {
|
|
||||||
'100': '0xd96f2B1c14Db8458374d9Aca76E26c3D18364307',
|
|
||||||
'1000': '0x4736dCf1b7A3d580672CcE6E7c65cd5cc9cFBa9D',
|
|
||||||
'10000': '0xD691F27f38B395864Ea86CfC7253969B409c362d',
|
|
||||||
'100000': undefined
|
|
||||||
},
|
|
||||||
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
|
||||||
symbol: 'USDC',
|
|
||||||
decimals: 6
|
|
||||||
},
|
|
||||||
cusdc: {
|
|
||||||
instanceAddress: {
|
|
||||||
'5000': '0xaEaaC358560e11f52454D997AAFF2c5731B6f8a6',
|
|
||||||
'50000': '0x1356c899D8C9467C7f71C195612F8A395aBf2f0a',
|
|
||||||
'500000': '0xA60C772958a3eD56c1F15dD055bA37AC8e523a0D',
|
|
||||||
'5000000': undefined
|
|
||||||
},
|
|
||||||
tokenAddress: '0x39AA39c021dfbaE8faC545936693aC917d5E7563',
|
|
||||||
symbol: 'cUSDC',
|
|
||||||
decimals: 8
|
|
||||||
},
|
|
||||||
usdt: {
|
|
||||||
instanceAddress: {
|
|
||||||
'100': '0x169AD27A470D064DEDE56a2D3ff727986b15D52B',
|
|
||||||
'1000': '0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f',
|
|
||||||
'10000': '0xF67721A2D8F736E75a49FdD7FAd2e31D8676542a',
|
|
||||||
'100000': '0x9AD122c22B14202B4490eDAf288FDb3C7cb3ff5E'
|
|
||||||
},
|
|
||||||
tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
||||||
symbol: 'USDT',
|
|
||||||
decimals: 6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
netId42: {
|
|
||||||
eth: {
|
|
||||||
instanceAddress: {
|
|
||||||
'0.1': '0x8b3f5393bA08c24cc7ff5A66a832562aAB7bC95f',
|
|
||||||
'1': '0xD6a6AC46d02253c938B96D12BE439F570227aE8E',
|
|
||||||
'10': '0xe1BE96331391E519471100c3c1528B66B8F4e5a7',
|
|
||||||
'100': '0xd037E0Ac98Dab2fCb7E296c69C6e52767Ae5414D'
|
|
||||||
},
|
|
||||||
symbol: 'ETH',
|
|
||||||
decimals: 18
|
|
||||||
},
|
|
||||||
dai: {
|
|
||||||
instanceAddress: {
|
|
||||||
'100': '0xdf2d3cC5F361CF95b3f62c4bB66deFe3FDE47e3D',
|
|
||||||
'1000': '0xD96291dFa35d180a71964D0894a1Ae54247C4ccD',
|
|
||||||
'10000': '0xb192794f72EA45e33C3DF6fe212B9c18f6F45AE3',
|
|
||||||
'100000': undefined
|
|
||||||
},
|
|
||||||
tokenAddress: '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa',
|
|
||||||
symbol: 'DAI',
|
|
||||||
decimals: 18
|
|
||||||
},
|
|
||||||
cdai: {
|
|
||||||
instanceAddress: {
|
|
||||||
'5000': '0x6Fc9386ABAf83147b3a89C36D422c625F44121C8',
|
|
||||||
'50000': '0x7182EA067e0f050997444FCb065985Fd677C16b6',
|
|
||||||
'500000': '0xC22ceFd90fbd1FdEeE554AE6Cc671179BC3b10Ae',
|
|
||||||
'5000000': undefined
|
|
||||||
},
|
|
||||||
tokenAddress: '0xe7bc397DBd069fC7d0109C0636d06888bb50668c',
|
|
||||||
symbol: 'cDAI',
|
|
||||||
decimals: 8
|
|
||||||
},
|
|
||||||
usdc: {
|
|
||||||
instanceAddress: {
|
|
||||||
'100': '0x137E2B6d185018e7f09f6cf175a970e7fC73826C',
|
|
||||||
'1000': '0xcC7f1633A5068E86E3830e692e3e3f8f520525Af',
|
|
||||||
'10000': '0x28C8f149a0ab8A9bdB006B8F984fFFCCE52ef5EF',
|
|
||||||
'100000': undefined
|
|
||||||
},
|
|
||||||
tokenAddress: '0x75B0622Cec14130172EaE9Cf166B92E5C112FaFF',
|
|
||||||
symbol: 'USDC',
|
|
||||||
decimals: 6
|
|
||||||
},
|
|
||||||
cusdc: {
|
|
||||||
instanceAddress: {
|
|
||||||
'5000': '0xc0648F28ABA385c8a1421Bbf1B59e3c474F89cB0',
|
|
||||||
'50000': '0x0C53853379c6b1A7B74E0A324AcbDD5Eabd4981D',
|
|
||||||
'500000': '0xf84016A0E03917cBe700D318EB1b7a53e6e3dEe1',
|
|
||||||
'5000000': undefined
|
|
||||||
},
|
|
||||||
tokenAddress: '0xcfC9bB230F00bFFDB560fCe2428b4E05F3442E35',
|
|
||||||
symbol: 'cUSDC',
|
|
||||||
decimals: 8
|
|
||||||
},
|
|
||||||
usdt: {
|
|
||||||
instanceAddress: {
|
|
||||||
'100': '0x327853Da7916a6A0935563FB1919A48843036b42',
|
|
||||||
'1000': '0x531AA4DF5858EA1d0031Dad16e3274609DE5AcC0',
|
|
||||||
'10000': '0x0958275F0362cf6f07D21373aEE0cf37dFe415dD',
|
|
||||||
'100000': '0x14aEd24B67EaF3FF28503eB92aeb217C47514364'
|
|
||||||
},
|
|
||||||
tokenAddress: '0x03c5F29e9296006876d8DF210BCFfD7EA5Db1Cf1',
|
|
||||||
symbol: 'USDT',
|
|
||||||
decimals: 6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +1,59 @@
|
||||||
// https://tornado.cash
|
// https://tornado.cash
|
||||||
/*
|
/*
|
||||||
* d888888P dP a88888b. dP
|
* d888888P dP a88888b. dP
|
||||||
* 88 88 d8' `88 88
|
* 88 88 d8' `88 88
|
||||||
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||||
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||||
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||||
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||||
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pragma solidity 0.5.17;
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
import "./Tornado.sol";
|
import "./Tornado.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
|
||||||
|
|
||||||
contract ERC20Tornado is Tornado {
|
contract ERC20Tornado is Tornado {
|
||||||
address public token;
|
using SafeERC20 for IERC20;
|
||||||
|
IERC20 public token;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
IVerifier _verifier,
|
IVerifier _verifier,
|
||||||
|
IHasher _hasher,
|
||||||
uint256 _denomination,
|
uint256 _denomination,
|
||||||
uint32 _merkleTreeHeight,
|
uint32 _merkleTreeHeight,
|
||||||
address _operator,
|
IERC20 _token
|
||||||
address _token
|
) Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight) {
|
||||||
) Tornado(_verifier, _denomination, _merkleTreeHeight, _operator) public {
|
|
||||||
token = _token;
|
token = _token;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _processDeposit() internal {
|
function _processDeposit() internal override {
|
||||||
require(msg.value == 0, "ETH value is supposed to be 0 for ERC20 instance");
|
require(msg.value == 0, "ETH value is supposed to be 0 for ERC20 instance");
|
||||||
_safeErc20TransferFrom(msg.sender, address(this), denomination);
|
token.safeTransferFrom(msg.sender, address(this), denomination);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _processWithdraw(address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) internal {
|
function _processWithdraw(
|
||||||
|
address payable _recipient,
|
||||||
|
address payable _relayer,
|
||||||
|
uint256 _fee,
|
||||||
|
uint256 _refund
|
||||||
|
) internal override {
|
||||||
require(msg.value == _refund, "Incorrect refund amount received by the contract");
|
require(msg.value == _refund, "Incorrect refund amount received by the contract");
|
||||||
|
|
||||||
_safeErc20Transfer(_recipient, denomination - _fee);
|
token.safeTransfer(_recipient, denomination - _fee);
|
||||||
if (_fee > 0) {
|
if (_fee > 0) {
|
||||||
_safeErc20Transfer(_relayer, _fee);
|
token.safeTransfer(_relayer, _fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_refund > 0) {
|
if (_refund > 0) {
|
||||||
(bool success, ) = _recipient.call.value(_refund)("");
|
(bool success, ) = _recipient.call{ value: _refund }("");
|
||||||
if (!success) {
|
if (!success) {
|
||||||
// let's return _refund back to the relayer
|
// let's return _refund back to the relayer
|
||||||
_relayer.transfer(_refund);
|
_relayer.transfer(_refund);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _safeErc20TransferFrom(address _from, address _to, uint256 _amount) internal {
|
|
||||||
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd /* transferFrom */, _from, _to, _amount));
|
|
||||||
require(success, "not enough allowed tokens");
|
|
||||||
|
|
||||||
// if contract returns some data lets make sure that is `true` according to standard
|
|
||||||
if (data.length > 0) {
|
|
||||||
require(data.length == 32, "data length should be either 0 or 32 bytes");
|
|
||||||
success = abi.decode(data, (bool));
|
|
||||||
require(success, "not enough allowed tokens. Token returns false.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _safeErc20Transfer(address _to, uint256 _amount) internal {
|
|
||||||
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb /* transfer */, _to, _amount));
|
|
||||||
require(success, "not enough tokens");
|
|
||||||
|
|
||||||
// if contract returns some data lets make sure that is `true` according to standard
|
|
||||||
if (data.length > 0) {
|
|
||||||
require(data.length == 32, "data length should be either 0 or 32 bytes");
|
|
||||||
success = abi.decode(data, (bool));
|
|
||||||
require(success, "not enough tokens. Token returns false.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,45 @@
|
||||||
// https://tornado.cash
|
// https://tornado.cash
|
||||||
/*
|
/*
|
||||||
* d888888P dP a88888b. dP
|
* d888888P dP a88888b. dP
|
||||||
* 88 88 d8' `88 88
|
* 88 88 d8' `88 88
|
||||||
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||||
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||||
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||||
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||||
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pragma solidity 0.5.17;
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
import "./Tornado.sol";
|
import "./Tornado.sol";
|
||||||
|
|
||||||
contract ETHTornado is Tornado {
|
contract ETHTornado is Tornado {
|
||||||
constructor(
|
constructor(
|
||||||
IVerifier _verifier,
|
IVerifier _verifier,
|
||||||
|
IHasher _hasher,
|
||||||
uint256 _denomination,
|
uint256 _denomination,
|
||||||
uint32 _merkleTreeHeight,
|
uint32 _merkleTreeHeight
|
||||||
address _operator
|
) Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight) {}
|
||||||
) Tornado(_verifier, _denomination, _merkleTreeHeight, _operator) public {
|
|
||||||
}
|
|
||||||
|
|
||||||
function _processDeposit() internal {
|
function _processDeposit() internal override {
|
||||||
require(msg.value == denomination, "Please send `mixDenomination` ETH along with transaction");
|
require(msg.value == denomination, "Please send `mixDenomination` ETH along with transaction");
|
||||||
}
|
}
|
||||||
|
|
||||||
function _processWithdraw(address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) internal {
|
function _processWithdraw(
|
||||||
|
address payable _recipient,
|
||||||
|
address payable _relayer,
|
||||||
|
uint256 _fee,
|
||||||
|
uint256 _refund
|
||||||
|
) internal override {
|
||||||
// sanity checks
|
// sanity checks
|
||||||
require(msg.value == 0, "Message value is supposed to be zero for ETH instance");
|
require(msg.value == 0, "Message value is supposed to be zero for ETH instance");
|
||||||
require(_refund == 0, "Refund value is supposed to be zero for ETH instance");
|
require(_refund == 0, "Refund value is supposed to be zero for ETH instance");
|
||||||
|
|
||||||
(bool success, ) = _recipient.call.value(denomination - _fee)("");
|
(bool success, ) = _recipient.call{ value: denomination - _fee }("");
|
||||||
require(success, "payment to _recipient did not go thru");
|
require(success, "payment to _recipient did not go thru");
|
||||||
if (_fee > 0) {
|
if (_fee > 0) {
|
||||||
(success, ) = _relayer.call.value(_fee)("");
|
(success, ) = _relayer.call{ value: _fee }("");
|
||||||
require(success, "payment to _relayer did not go thru");
|
require(success, "payment to _relayer did not go thru");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,74 @@
|
||||||
// https://tornado.cash
|
// https://tornado.cash
|
||||||
/*
|
/*
|
||||||
* d888888P dP a88888b. dP
|
* d888888P dP a88888b. dP
|
||||||
* 88 88 d8' `88 88
|
* 88 88 d8' `88 88
|
||||||
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||||
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||||
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||||
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||||
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pragma solidity 0.5.17;
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
library Hasher {
|
interface IHasher {
|
||||||
function MiMCSponge(uint256 in_xL, uint256 in_xR) public pure returns (uint256 xL, uint256 xR);
|
function MiMCSponge(uint256 in_xL, uint256 in_xR) external pure returns (uint256 xL, uint256 xR);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract MerkleTreeWithHistory {
|
contract MerkleTreeWithHistory {
|
||||||
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||||
uint256 public constant ZERO_VALUE = 21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE
|
uint256 public constant ZERO_VALUE = 21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE
|
||||||
|
IHasher public immutable hasher;
|
||||||
|
|
||||||
uint32 public levels;
|
uint32 public levels;
|
||||||
|
|
||||||
// the following variables are made public for easier testing and debugging and
|
// the following variables are made public for easier testing and debugging and
|
||||||
// are not supposed to be accessed in regular code
|
// are not supposed to be accessed in regular code
|
||||||
bytes32[] public filledSubtrees;
|
|
||||||
bytes32[] public zeros;
|
// filledSubtrees and roots could be bytes32[size], but using mappings makes it cheaper because
|
||||||
|
// it removes index range check on every interaction
|
||||||
|
mapping(uint256 => bytes32) public filledSubtrees;
|
||||||
|
mapping(uint256 => bytes32) public roots;
|
||||||
|
uint32 public constant ROOT_HISTORY_SIZE = 30;
|
||||||
uint32 public currentRootIndex = 0;
|
uint32 public currentRootIndex = 0;
|
||||||
uint32 public nextIndex = 0;
|
uint32 public nextIndex = 0;
|
||||||
uint32 public constant ROOT_HISTORY_SIZE = 100;
|
|
||||||
bytes32[ROOT_HISTORY_SIZE] public roots;
|
|
||||||
|
|
||||||
constructor(uint32 _treeLevels) public {
|
constructor(uint32 _levels, IHasher _hasher) {
|
||||||
require(_treeLevels > 0, "_treeLevels should be greater than zero");
|
require(_levels > 0, "_levels should be greater than zero");
|
||||||
require(_treeLevels < 32, "_treeLevels should be less than 32");
|
require(_levels < 32, "_levels should be less than 32");
|
||||||
levels = _treeLevels;
|
levels = _levels;
|
||||||
|
hasher = _hasher;
|
||||||
|
|
||||||
bytes32 currentZero = bytes32(ZERO_VALUE);
|
for (uint32 i = 0; i < _levels; i++) {
|
||||||
zeros.push(currentZero);
|
filledSubtrees[i] = zeros(i);
|
||||||
filledSubtrees.push(currentZero);
|
|
||||||
|
|
||||||
for (uint32 i = 1; i < levels; i++) {
|
|
||||||
currentZero = hashLeftRight(currentZero, currentZero);
|
|
||||||
zeros.push(currentZero);
|
|
||||||
filledSubtrees.push(currentZero);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
roots[0] = hashLeftRight(currentZero, currentZero);
|
roots[0] = zeros(_levels - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@dev Hash 2 tree leaves, returns MiMC(_left, _right)
|
@dev Hash 2 tree leaves, returns MiMC(_left, _right)
|
||||||
*/
|
*/
|
||||||
function hashLeftRight(bytes32 _left, bytes32 _right) public pure returns (bytes32) {
|
function hashLeftRight(
|
||||||
|
IHasher _hasher,
|
||||||
|
bytes32 _left,
|
||||||
|
bytes32 _right
|
||||||
|
) public pure returns (bytes32) {
|
||||||
require(uint256(_left) < FIELD_SIZE, "_left should be inside the field");
|
require(uint256(_left) < FIELD_SIZE, "_left should be inside the field");
|
||||||
require(uint256(_right) < FIELD_SIZE, "_right should be inside the field");
|
require(uint256(_right) < FIELD_SIZE, "_right should be inside the field");
|
||||||
uint256 R = uint256(_left);
|
uint256 R = uint256(_left);
|
||||||
uint256 C = 0;
|
uint256 C = 0;
|
||||||
(R, C) = Hasher.MiMCSponge(R, C);
|
(R, C) = _hasher.MiMCSponge(R, C);
|
||||||
R = addmod(R, uint256(_right), FIELD_SIZE);
|
R = addmod(R, uint256(_right), FIELD_SIZE);
|
||||||
(R, C) = Hasher.MiMCSponge(R, C);
|
(R, C) = _hasher.MiMCSponge(R, C);
|
||||||
return bytes32(R);
|
return bytes32(R);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _insert(bytes32 _leaf) internal returns(uint32 index) {
|
function _insert(bytes32 _leaf) internal returns (uint32 index) {
|
||||||
uint32 currentIndex = nextIndex;
|
uint32 _nextIndex = nextIndex;
|
||||||
require(currentIndex != uint32(2)**levels, "Merkle tree is full. No more leafs can be added");
|
require(_nextIndex != uint32(2)**levels, "Merkle tree is full. No more leaves can be added");
|
||||||
nextIndex += 1;
|
uint32 currentIndex = _nextIndex;
|
||||||
bytes32 currentLevelHash = _leaf;
|
bytes32 currentLevelHash = _leaf;
|
||||||
bytes32 left;
|
bytes32 left;
|
||||||
bytes32 right;
|
bytes32 right;
|
||||||
|
@ -73,32 +76,32 @@ contract MerkleTreeWithHistory {
|
||||||
for (uint32 i = 0; i < levels; i++) {
|
for (uint32 i = 0; i < levels; i++) {
|
||||||
if (currentIndex % 2 == 0) {
|
if (currentIndex % 2 == 0) {
|
||||||
left = currentLevelHash;
|
left = currentLevelHash;
|
||||||
right = zeros[i];
|
right = zeros(i);
|
||||||
|
|
||||||
filledSubtrees[i] = currentLevelHash;
|
filledSubtrees[i] = currentLevelHash;
|
||||||
} else {
|
} else {
|
||||||
left = filledSubtrees[i];
|
left = filledSubtrees[i];
|
||||||
right = currentLevelHash;
|
right = currentLevelHash;
|
||||||
}
|
}
|
||||||
|
currentLevelHash = hashLeftRight(hasher, left, right);
|
||||||
currentLevelHash = hashLeftRight(left, right);
|
|
||||||
|
|
||||||
currentIndex /= 2;
|
currentIndex /= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE;
|
uint32 newRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE;
|
||||||
roots[currentRootIndex] = currentLevelHash;
|
currentRootIndex = newRootIndex;
|
||||||
return nextIndex - 1;
|
roots[newRootIndex] = currentLevelHash;
|
||||||
|
nextIndex = _nextIndex + 1;
|
||||||
|
return _nextIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@dev Whether the root is present in the root history
|
@dev Whether the root is present in the root history
|
||||||
*/
|
*/
|
||||||
function isKnownRoot(bytes32 _root) public view returns(bool) {
|
function isKnownRoot(bytes32 _root) public view returns (bool) {
|
||||||
if (_root == 0) {
|
if (_root == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint32 i = currentRootIndex;
|
uint32 _currentRootIndex = currentRootIndex;
|
||||||
|
uint32 i = _currentRootIndex;
|
||||||
do {
|
do {
|
||||||
if (_root == roots[i]) {
|
if (_root == roots[i]) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -107,14 +110,51 @@ contract MerkleTreeWithHistory {
|
||||||
i = ROOT_HISTORY_SIZE;
|
i = ROOT_HISTORY_SIZE;
|
||||||
}
|
}
|
||||||
i--;
|
i--;
|
||||||
} while (i != currentRootIndex);
|
} while (i != _currentRootIndex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@dev Returns the last root
|
@dev Returns the last root
|
||||||
*/
|
*/
|
||||||
function getLastRoot() public view returns(bytes32) {
|
function getLastRoot() public view returns (bytes32) {
|
||||||
return roots[currentRootIndex];
|
return roots[currentRootIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @dev provides Zero (Empty) elements for a MiMC MerkleTree. Up to 32 levels
|
||||||
|
function zeros(uint256 i) public pure returns (bytes32) {
|
||||||
|
if (i == 0) return bytes32(0x2fe54c60d3acabf3343a35b6eba15db4821b340f76e741e2249685ed4899af6c);
|
||||||
|
else if (i == 1) return bytes32(0x256a6135777eee2fd26f54b8b7037a25439d5235caee224154186d2b8a52e31d);
|
||||||
|
else if (i == 2) return bytes32(0x1151949895e82ab19924de92c40a3d6f7bcb60d92b00504b8199613683f0c200);
|
||||||
|
else if (i == 3) return bytes32(0x20121ee811489ff8d61f09fb89e313f14959a0f28bb428a20dba6b0b068b3bdb);
|
||||||
|
else if (i == 4) return bytes32(0x0a89ca6ffa14cc462cfedb842c30ed221a50a3d6bf022a6a57dc82ab24c157c9);
|
||||||
|
else if (i == 5) return bytes32(0x24ca05c2b5cd42e890d6be94c68d0689f4f21c9cec9c0f13fe41d566dfb54959);
|
||||||
|
else if (i == 6) return bytes32(0x1ccb97c932565a92c60156bdba2d08f3bf1377464e025cee765679e604a7315c);
|
||||||
|
else if (i == 7) return bytes32(0x19156fbd7d1a8bf5cba8909367de1b624534ebab4f0f79e003bccdd1b182bdb4);
|
||||||
|
else if (i == 8) return bytes32(0x261af8c1f0912e465744641409f622d466c3920ac6e5ff37e36604cb11dfff80);
|
||||||
|
else if (i == 9) return bytes32(0x0058459724ff6ca5a1652fcbc3e82b93895cf08e975b19beab3f54c217d1c007);
|
||||||
|
else if (i == 10) return bytes32(0x1f04ef20dee48d39984d8eabe768a70eafa6310ad20849d4573c3c40c2ad1e30);
|
||||||
|
else if (i == 11) return bytes32(0x1bea3dec5dab51567ce7e200a30f7ba6d4276aeaa53e2686f962a46c66d511e5);
|
||||||
|
else if (i == 12) return bytes32(0x0ee0f941e2da4b9e31c3ca97a40d8fa9ce68d97c084177071b3cb46cd3372f0f);
|
||||||
|
else if (i == 13) return bytes32(0x1ca9503e8935884501bbaf20be14eb4c46b89772c97b96e3b2ebf3a36a948bbd);
|
||||||
|
else if (i == 14) return bytes32(0x133a80e30697cd55d8f7d4b0965b7be24057ba5dc3da898ee2187232446cb108);
|
||||||
|
else if (i == 15) return bytes32(0x13e6d8fc88839ed76e182c2a779af5b2c0da9dd18c90427a644f7e148a6253b6);
|
||||||
|
else if (i == 16) return bytes32(0x1eb16b057a477f4bc8f572ea6bee39561098f78f15bfb3699dcbb7bd8db61854);
|
||||||
|
else if (i == 17) return bytes32(0x0da2cb16a1ceaabf1c16b838f7a9e3f2a3a3088d9e0a6debaa748114620696ea);
|
||||||
|
else if (i == 18) return bytes32(0x24a3b3d822420b14b5d8cb6c28a574f01e98ea9e940551d2ebd75cee12649f9d);
|
||||||
|
else if (i == 19) return bytes32(0x198622acbd783d1b0d9064105b1fc8e4d8889de95c4c519b3f635809fe6afc05);
|
||||||
|
else if (i == 20) return bytes32(0x29d7ed391256ccc3ea596c86e933b89ff339d25ea8ddced975ae2fe30b5296d4);
|
||||||
|
else if (i == 21) return bytes32(0x19be59f2f0413ce78c0c3703a3a5451b1d7f39629fa33abd11548a76065b2967);
|
||||||
|
else if (i == 22) return bytes32(0x1ff3f61797e538b70e619310d33f2a063e7eb59104e112e95738da1254dc3453);
|
||||||
|
else if (i == 23) return bytes32(0x10c16ae9959cf8358980d9dd9616e48228737310a10e2b6b731c1a548f036c48);
|
||||||
|
else if (i == 24) return bytes32(0x0ba433a63174a90ac20992e75e3095496812b652685b5e1a2eae0b1bf4e8fcd1);
|
||||||
|
else if (i == 25) return bytes32(0x019ddb9df2bc98d987d0dfeca9d2b643deafab8f7036562e627c3667266a044c);
|
||||||
|
else if (i == 26) return bytes32(0x2d3c88b23175c5a5565db928414c66d1912b11acf974b2e644caaac04739ce99);
|
||||||
|
else if (i == 27) return bytes32(0x2eab55f6ae4e66e32c5189eed5c470840863445760f5ed7e7b69b2a62600f354);
|
||||||
|
else if (i == 28) return bytes32(0x002df37a2642621802383cf952bf4dd1f32e05433beeb1fd41031fb7eace979d);
|
||||||
|
else if (i == 29) return bytes32(0x104aeb41435db66c3e62feccc1d6f5d98d0a0ed75d1374db457cf462e3a1f427);
|
||||||
|
else if (i == 30) return bytes32(0x1f3c6fd858e9a7d4b0d1f38e256a09d81d5a5e3c963987e2d4b814cfab7c6ebb);
|
||||||
|
else if (i == 31) return bytes32(0x2c7a07d20dff79d01fecedc1134284a8d08436606c93693b67e333f671bf69cc);
|
||||||
|
else revert("Index out of bounds");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
pragma solidity >=0.4.21 <0.6.0;
|
|
||||||
|
|
||||||
contract Migrations {
|
|
||||||
address public owner;
|
|
||||||
uint public last_completed_migration;
|
|
||||||
|
|
||||||
constructor() public {
|
|
||||||
owner = msg.sender;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier restricted() {
|
|
||||||
if (msg.sender == owner) _;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCompleted(uint completed) public restricted {
|
|
||||||
last_completed_migration = completed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function upgrade(address new_address) public restricted {
|
|
||||||
Migrations upgraded = Migrations(new_address);
|
|
||||||
upgraded.setCompleted(last_completed_migration);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,8 @@
|
||||||
pragma solidity ^0.5.0;
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
contract BadRecipient {
|
contract BadRecipient {
|
||||||
function() external {
|
fallback() external {
|
||||||
require(false, "this contract does not accept ETH");
|
require(false, "this contract does not accept ETH");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
pragma solidity ^0.5.0;
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20Mintable.sol";
|
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
|
|
||||||
|
|
||||||
contract ERC20Mock is ERC20Detailed, ERC20Mintable {
|
contract ERC20Mock is ERC20("DAIMock", "DAIM") {
|
||||||
constructor() ERC20Detailed("DAIMock", "DAIM", 18) public {
|
function mint(address account, uint256 amount) public {
|
||||||
|
_mint(account, amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
|
interface IDeployer {
|
||||||
|
function deploy(bytes memory _initCode, bytes32 _salt) external returns (address payable createdContract);
|
||||||
|
}
|
|
@ -1,20 +1,33 @@
|
||||||
pragma solidity 0.5.17;
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
contract ERC20Basic {
|
pragma solidity ^0.7.0;
|
||||||
uint public _totalSupply;
|
|
||||||
function totalSupply() public view returns (uint);
|
interface ERC20Basic {
|
||||||
function balanceOf(address who) public view returns (uint);
|
function _totalSupply() external returns (uint256);
|
||||||
function transfer(address to, uint value) public;
|
|
||||||
event Transfer(address indexed from, address indexed to, uint value);
|
function totalSupply() external view returns (uint256);
|
||||||
|
|
||||||
|
function balanceOf(address who) external view returns (uint256);
|
||||||
|
|
||||||
|
function transfer(address to, uint256 value) external;
|
||||||
|
|
||||||
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title ERC20 interface
|
* @title ERC20 interface
|
||||||
* @dev see https://github.com/ethereum/EIPs/issues/20
|
* @dev see https://github.com/ethereum/EIPs/issues/20
|
||||||
*/
|
*/
|
||||||
contract IUSDT is ERC20Basic {
|
interface IUSDT is ERC20Basic {
|
||||||
function allowance(address owner, address spender) public view returns (uint);
|
function allowance(address owner, address spender) external view returns (uint256);
|
||||||
function transferFrom(address from, address to, uint value) public;
|
|
||||||
function approve(address spender, uint value) public;
|
function transferFrom(
|
||||||
event Approval(address indexed owner, address indexed spender, uint value);
|
address from,
|
||||||
|
address to,
|
||||||
|
uint256 value
|
||||||
|
) external;
|
||||||
|
|
||||||
|
function approve(address spender, uint256 value) external;
|
||||||
|
|
||||||
|
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
pragma solidity 0.5.17;
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
import '../MerkleTreeWithHistory.sol';
|
import "../MerkleTreeWithHistory.sol";
|
||||||
|
|
||||||
contract MerkleTreeWithHistoryMock is MerkleTreeWithHistory {
|
contract MerkleTreeWithHistoryMock is MerkleTreeWithHistory {
|
||||||
|
constructor(uint32 _treeLevels, IHasher _hasher) MerkleTreeWithHistory(_treeLevels, _hasher) {}
|
||||||
constructor (uint32 _treeLevels) MerkleTreeWithHistory(_treeLevels) public {}
|
|
||||||
|
|
||||||
function insert(bytes32 _leaf) public {
|
function insert(bytes32 _leaf) public {
|
||||||
_insert(_leaf);
|
_insert(_leaf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,31 @@
|
||||||
// https://tornado.cash
|
// https://tornado.cash
|
||||||
/*
|
/*
|
||||||
* d888888P dP a88888b. dP
|
* d888888P dP a88888b. dP
|
||||||
* 88 88 d8' `88 88
|
* 88 88 d8' `88 88
|
||||||
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||||
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||||
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||||
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||||
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pragma solidity 0.5.17;
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
import "./MerkleTreeWithHistory.sol";
|
import "./MerkleTreeWithHistory.sol";
|
||||||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||||
|
|
||||||
contract IVerifier {
|
interface IVerifier {
|
||||||
function verifyProof(bytes memory _proof, uint256[6] memory _input) public returns(bool);
|
function verifyProof(bytes memory _proof, uint256[6] memory _input) external returns (bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
abstract contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
||||||
|
IVerifier public immutable verifier;
|
||||||
uint256 public denomination;
|
uint256 public denomination;
|
||||||
|
|
||||||
mapping(bytes32 => bool) public nullifierHashes;
|
mapping(bytes32 => bool) public nullifierHashes;
|
||||||
// we store all commitments just to prevent accidental deposits with the same commitment
|
// we store all commitments just to prevent accidental deposits with the same commitment
|
||||||
mapping(bytes32 => bool) public commitments;
|
mapping(bytes32 => bool) public commitments;
|
||||||
IVerifier public verifier;
|
|
||||||
|
|
||||||
// operator can update snark verification key
|
|
||||||
// after the final trusted setup ceremony operator rights are supposed to be transferred to zero address
|
|
||||||
address public operator;
|
|
||||||
modifier onlyOperator {
|
|
||||||
require(msg.sender == operator, "Only operator can call this function.");
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp);
|
event Deposit(bytes32 indexed commitment, uint32 leafIndex, uint256 timestamp);
|
||||||
event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee);
|
event Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee);
|
||||||
|
@ -39,19 +33,18 @@ contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
||||||
/**
|
/**
|
||||||
@dev The constructor
|
@dev The constructor
|
||||||
@param _verifier the address of SNARK verifier for this contract
|
@param _verifier the address of SNARK verifier for this contract
|
||||||
|
@param _hasher the address of MiMC hash contract
|
||||||
@param _denomination transfer amount for each deposit
|
@param _denomination transfer amount for each deposit
|
||||||
@param _merkleTreeHeight the height of deposits' Merkle Tree
|
@param _merkleTreeHeight the height of deposits' Merkle Tree
|
||||||
@param _operator operator address (see operator comment above)
|
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
IVerifier _verifier,
|
IVerifier _verifier,
|
||||||
|
IHasher _hasher,
|
||||||
uint256 _denomination,
|
uint256 _denomination,
|
||||||
uint32 _merkleTreeHeight,
|
uint32 _merkleTreeHeight
|
||||||
address _operator
|
) MerkleTreeWithHistory(_merkleTreeHeight, _hasher) {
|
||||||
) MerkleTreeWithHistory(_merkleTreeHeight) public {
|
|
||||||
require(_denomination > 0, "denomination should be greater than 0");
|
require(_denomination > 0, "denomination should be greater than 0");
|
||||||
verifier = _verifier;
|
verifier = _verifier;
|
||||||
operator = _operator;
|
|
||||||
denomination = _denomination;
|
denomination = _denomination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +63,7 @@ contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dev this function is defined in a child contract */
|
/** @dev this function is defined in a child contract */
|
||||||
function _processDeposit() internal;
|
function _processDeposit() internal virtual;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs
|
@dev Withdraw a deposit from the contract. `proof` is a zkSNARK proof data, and input is an array of circuit public inputs
|
||||||
|
@ -80,11 +73,25 @@ contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
||||||
- the recipient of funds
|
- the recipient of funds
|
||||||
- optional fee that goes to the transaction sender (usually a relay)
|
- optional fee that goes to the transaction sender (usually a relay)
|
||||||
*/
|
*/
|
||||||
function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) external payable nonReentrant {
|
function withdraw(
|
||||||
|
bytes calldata _proof,
|
||||||
|
bytes32 _root,
|
||||||
|
bytes32 _nullifierHash,
|
||||||
|
address payable _recipient,
|
||||||
|
address payable _relayer,
|
||||||
|
uint256 _fee,
|
||||||
|
uint256 _refund
|
||||||
|
) external payable nonReentrant {
|
||||||
require(_fee <= denomination, "Fee exceeds transfer value");
|
require(_fee <= denomination, "Fee exceeds transfer value");
|
||||||
require(!nullifierHashes[_nullifierHash], "The note has been already spent");
|
require(!nullifierHashes[_nullifierHash], "The note has been already spent");
|
||||||
require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one
|
require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one
|
||||||
require(verifier.verifyProof(_proof, [uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _fee, _refund]), "Invalid withdraw proof");
|
require(
|
||||||
|
verifier.verifyProof(
|
||||||
|
_proof,
|
||||||
|
[uint256(_root), uint256(_nullifierHash), uint256(_recipient), uint256(_relayer), _fee, _refund]
|
||||||
|
),
|
||||||
|
"Invalid withdraw proof"
|
||||||
|
);
|
||||||
|
|
||||||
nullifierHashes[_nullifierHash] = true;
|
nullifierHashes[_nullifierHash] = true;
|
||||||
_processWithdraw(_recipient, _relayer, _fee, _refund);
|
_processWithdraw(_recipient, _relayer, _fee, _refund);
|
||||||
|
@ -92,33 +99,25 @@ contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dev this function is defined in a child contract */
|
/** @dev this function is defined in a child contract */
|
||||||
function _processWithdraw(address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund) internal;
|
function _processWithdraw(
|
||||||
|
address payable _recipient,
|
||||||
|
address payable _relayer,
|
||||||
|
uint256 _fee,
|
||||||
|
uint256 _refund
|
||||||
|
) internal virtual;
|
||||||
|
|
||||||
/** @dev whether a note is already spent */
|
/** @dev whether a note is already spent */
|
||||||
function isSpent(bytes32 _nullifierHash) public view returns(bool) {
|
function isSpent(bytes32 _nullifierHash) public view returns (bool) {
|
||||||
return nullifierHashes[_nullifierHash];
|
return nullifierHashes[_nullifierHash];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dev whether an array of notes is already spent */
|
/** @dev whether an array of notes is already spent */
|
||||||
function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns(bool[] memory spent) {
|
function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns (bool[] memory spent) {
|
||||||
spent = new bool[](_nullifierHashes.length);
|
spent = new bool[](_nullifierHashes.length);
|
||||||
for(uint i = 0; i < _nullifierHashes.length; i++) {
|
for (uint256 i = 0; i < _nullifierHashes.length; i++) {
|
||||||
if (isSpent(_nullifierHashes[i])) {
|
if (isSpent(_nullifierHashes[i])) {
|
||||||
spent[i] = true;
|
spent[i] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
@dev allow operator to update SNARK verification keys. This is needed to update keys after the final trusted setup ceremony is held.
|
|
||||||
After that operator rights are supposed to be transferred to zero address
|
|
||||||
*/
|
|
||||||
function updateVerifier(address _newVerifier) external onlyOperator {
|
|
||||||
verifier = IVerifier(_newVerifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @dev operator can change his address */
|
|
||||||
function changeOperator(address _newOperator) external onlyOperator {
|
|
||||||
operator = _newOperator;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../build/circuits/Verifier.sol
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
/**
|
||||||
|
*Submitted for verification at Etherscan.io on 2020-05-12
|
||||||
|
*/
|
||||||
|
|
||||||
|
// https://tornado.cash Verifier.sol generated by trusted setup ceremony.
|
||||||
|
/*
|
||||||
|
* d888888P dP a88888b. dP
|
||||||
|
* 88 88 d8' `88 88
|
||||||
|
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||||
|
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||||
|
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||||
|
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||||
|
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||||
|
*/
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright 2017 Christian Reitwiessner
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to
|
||||||
|
// deal in the Software without restriction, including without limitation the
|
||||||
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
// sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
// IN THE SOFTWARE.
|
||||||
|
|
||||||
|
// 2019 OKIMS
|
||||||
|
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
|
library Pairing {
|
||||||
|
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||||
|
|
||||||
|
struct G1Point {
|
||||||
|
uint256 X;
|
||||||
|
uint256 Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding of field elements is: X[0] * z + X[1]
|
||||||
|
struct G2Point {
|
||||||
|
uint256[2] X;
|
||||||
|
uint256[2] Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @return The negation of p, i.e. p.plus(p.negate()) should be zero.
|
||||||
|
*/
|
||||||
|
function negate(G1Point memory p) internal pure returns (G1Point memory) {
|
||||||
|
// The prime q in the base field F_q for G1
|
||||||
|
if (p.X == 0 && p.Y == 0) {
|
||||||
|
return G1Point(0, 0);
|
||||||
|
} else {
|
||||||
|
return G1Point(p.X, PRIME_Q - (p.Y % PRIME_Q));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @return r the sum of two points of G1
|
||||||
|
*/
|
||||||
|
function plus(
|
||||||
|
G1Point memory p1,
|
||||||
|
G1Point memory p2
|
||||||
|
) internal view returns (G1Point memory r) {
|
||||||
|
uint256[4] memory input;
|
||||||
|
input[0] = p1.X;
|
||||||
|
input[1] = p1.Y;
|
||||||
|
input[2] = p2.X;
|
||||||
|
input[3] = p2.Y;
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
// solium-disable-next-line security/no-inline-assembly
|
||||||
|
assembly {
|
||||||
|
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
|
||||||
|
// Use "invalid" to make gas estimation work
|
||||||
|
switch success case 0 { invalid() }
|
||||||
|
}
|
||||||
|
|
||||||
|
require(success, "pairing-add-failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @return r the product of a point on G1 and a scalar, i.e.
|
||||||
|
* p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
|
||||||
|
* points p.
|
||||||
|
*/
|
||||||
|
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
|
||||||
|
uint256[3] memory input;
|
||||||
|
input[0] = p.X;
|
||||||
|
input[1] = p.Y;
|
||||||
|
input[2] = s;
|
||||||
|
bool success;
|
||||||
|
// solium-disable-next-line security/no-inline-assembly
|
||||||
|
assembly {
|
||||||
|
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
|
||||||
|
// Use "invalid" to make gas estimation work
|
||||||
|
switch success case 0 { invalid() }
|
||||||
|
}
|
||||||
|
require(success, "pairing-mul-failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @return The result of computing the pairing check
|
||||||
|
* e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
|
||||||
|
* For example,
|
||||||
|
* pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
|
||||||
|
*/
|
||||||
|
function pairing(
|
||||||
|
G1Point memory a1,
|
||||||
|
G2Point memory a2,
|
||||||
|
G1Point memory b1,
|
||||||
|
G2Point memory b2,
|
||||||
|
G1Point memory c1,
|
||||||
|
G2Point memory c2,
|
||||||
|
G1Point memory d1,
|
||||||
|
G2Point memory d2
|
||||||
|
) internal view returns (bool) {
|
||||||
|
G1Point[4] memory p1 = [a1, b1, c1, d1];
|
||||||
|
G2Point[4] memory p2 = [a2, b2, c2, d2];
|
||||||
|
|
||||||
|
uint256 inputSize = 24;
|
||||||
|
uint256[] memory input = new uint256[](inputSize);
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < 4; i++) {
|
||||||
|
uint256 j = i * 6;
|
||||||
|
input[j + 0] = p1[i].X;
|
||||||
|
input[j + 1] = p1[i].Y;
|
||||||
|
input[j + 2] = p2[i].X[0];
|
||||||
|
input[j + 3] = p2[i].X[1];
|
||||||
|
input[j + 4] = p2[i].Y[0];
|
||||||
|
input[j + 5] = p2[i].Y[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256[1] memory out;
|
||||||
|
bool success;
|
||||||
|
|
||||||
|
// solium-disable-next-line security/no-inline-assembly
|
||||||
|
assembly {
|
||||||
|
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
|
||||||
|
// Use "invalid" to make gas estimation work
|
||||||
|
switch success case 0 { invalid() }
|
||||||
|
}
|
||||||
|
|
||||||
|
require(success, "pairing-opcode-failed");
|
||||||
|
|
||||||
|
return out[0] != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract Verifier {
|
||||||
|
uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||||
|
uint256 constant PRIME_Q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
|
||||||
|
using Pairing for *;
|
||||||
|
|
||||||
|
struct VerifyingKey {
|
||||||
|
Pairing.G1Point alfa1;
|
||||||
|
Pairing.G2Point beta2;
|
||||||
|
Pairing.G2Point gamma2;
|
||||||
|
Pairing.G2Point delta2;
|
||||||
|
Pairing.G1Point[7] IC;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Proof {
|
||||||
|
Pairing.G1Point A;
|
||||||
|
Pairing.G2Point B;
|
||||||
|
Pairing.G1Point C;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
|
||||||
|
vk.alfa1 = Pairing.G1Point(uint256(20692898189092739278193869274495556617788530808486270118371701516666252877969), uint256(11713062878292653967971378194351968039596396853904572879488166084231740557279));
|
||||||
|
vk.beta2 = Pairing.G2Point([uint256(12168528810181263706895252315640534818222943348193302139358377162645029937006), uint256(281120578337195720357474965979947690431622127986816839208576358024608803542)], [uint256(16129176515713072042442734839012966563817890688785805090011011570989315559913), uint256(9011703453772030375124466642203641636825223906145908770308724549646909480510)]);
|
||||||
|
vk.gamma2 = Pairing.G2Point([uint256(11559732032986387107991004021392285783925812861821192530917403151452391805634), uint256(10857046999023057135944570762232829481370756359578518086990519993285655852781)], [uint256(4082367875863433681332203403145435568316851327593401208105741076214120093531), uint256(8495653923123431417604973247489272438418190587263600148770280649306958101930)]);
|
||||||
|
vk.delta2 = Pairing.G2Point([uint256(21280594949518992153305586783242820682644996932183186320680800072133486887432), uint256(150879136433974552800030963899771162647715069685890547489132178314736470662)], [uint256(1081836006956609894549771334721413187913047383331561601606260283167615953295), uint256(11434086686358152335540554643130007307617078324975981257823476472104616196090)]);
|
||||||
|
vk.IC[0] = Pairing.G1Point(uint256(16225148364316337376768119297456868908427925829817748684139175309620217098814), uint256(5167268689450204162046084442581051565997733233062478317813755636162413164690));
|
||||||
|
vk.IC[1] = Pairing.G1Point(uint256(12882377842072682264979317445365303375159828272423495088911985689463022094260), uint256(19488215856665173565526758360510125932214252767275816329232454875804474844786));
|
||||||
|
vk.IC[2] = Pairing.G1Point(uint256(13083492661683431044045992285476184182144099829507350352128615182516530014777), uint256(602051281796153692392523702676782023472744522032670801091617246498551238913));
|
||||||
|
vk.IC[3] = Pairing.G1Point(uint256(9732465972180335629969421513785602934706096902316483580882842789662669212890), uint256(2776526698606888434074200384264824461688198384989521091253289776235602495678));
|
||||||
|
vk.IC[4] = Pairing.G1Point(uint256(8586364274534577154894611080234048648883781955345622578531233113180532234842), uint256(21276134929883121123323359450658320820075698490666870487450985603988214349407));
|
||||||
|
vk.IC[5] = Pairing.G1Point(uint256(4910628533171597675018724709631788948355422829499855033965018665300386637884), uint256(20532468890024084510431799098097081600480376127870299142189696620752500664302));
|
||||||
|
vk.IC[6] = Pairing.G1Point(uint256(15335858102289947642505450692012116222827233918185150176888641903531542034017), uint256(5311597067667671581646709998171703828965875677637292315055030353779531404812));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @returns Whether the proof is valid given the hardcoded verifying key
|
||||||
|
* above and the public inputs
|
||||||
|
*/
|
||||||
|
function verifyProof(
|
||||||
|
bytes memory proof,
|
||||||
|
uint256[6] memory input
|
||||||
|
) public view returns (bool) {
|
||||||
|
uint256[8] memory p = abi.decode(proof, (uint256[8]));
|
||||||
|
|
||||||
|
// Make sure that each element in the proof is less than the prime q
|
||||||
|
for (uint8 i = 0; i < p.length; i++) {
|
||||||
|
require(p[i] < PRIME_Q, "verifier-proof-element-gte-prime-q");
|
||||||
|
}
|
||||||
|
|
||||||
|
Proof memory _proof;
|
||||||
|
_proof.A = Pairing.G1Point(p[0], p[1]);
|
||||||
|
_proof.B = Pairing.G2Point([p[2], p[3]], [p[4], p[5]]);
|
||||||
|
_proof.C = Pairing.G1Point(p[6], p[7]);
|
||||||
|
|
||||||
|
VerifyingKey memory vk = verifyingKey();
|
||||||
|
|
||||||
|
// Compute the linear combination vk_x
|
||||||
|
Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
|
||||||
|
vk_x = Pairing.plus(vk_x, vk.IC[0]);
|
||||||
|
|
||||||
|
// Make sure that every input is less than the snark scalar field
|
||||||
|
for (uint256 i = 0; i < input.length; i++) {
|
||||||
|
require(input[i] < SNARK_SCALAR_FIELD, "verifier-gte-snark-scalar-field");
|
||||||
|
vk_x = Pairing.plus(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pairing.pairing(
|
||||||
|
Pairing.negate(_proof.A),
|
||||||
|
_proof.B,
|
||||||
|
vk.alfa1,
|
||||||
|
vk.beta2,
|
||||||
|
vk_x,
|
||||||
|
vk.gamma2,
|
||||||
|
_proof.C,
|
||||||
|
vk.delta2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// https://tornado.cash
|
||||||
|
/*
|
||||||
|
* d888888P dP a88888b. dP
|
||||||
|
* 88 88 d8' `88 88
|
||||||
|
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
|
||||||
|
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
|
||||||
|
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
|
||||||
|
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
|
||||||
|
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||||
|
*/
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.0;
|
||||||
|
|
||||||
|
import "./ERC20Tornado.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
|
||||||
|
contract cTornado is ERC20Tornado {
|
||||||
|
address public immutable governance = 0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
|
||||||
|
IERC20 public immutable comp;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
IERC20 _comp,
|
||||||
|
IVerifier _verifier,
|
||||||
|
IHasher _hasher,
|
||||||
|
uint256 _denomination,
|
||||||
|
uint32 _merkleTreeHeight,
|
||||||
|
IERC20 _token
|
||||||
|
) ERC20Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight, _token) {
|
||||||
|
require(address(_comp) != address(0), "Invalid COMP token address");
|
||||||
|
comp = _comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Moves earned yield of the COMP token to the tornado governance contract
|
||||||
|
/// To make it work you might need to call `comptroller.claimComp(cPoolAddress)` first
|
||||||
|
function claimComp() external {
|
||||||
|
comp.transfer(governance, comp.balanceOf(address(this)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
const eth = true
|
||||||
|
const poolSize = '1000000000000000000'
|
||||||
|
const hasherAddress = '0x83584f83f26aF4eDDA9CBe8C730bc87C364b28fe'
|
||||||
|
const verifierAddress = '0xce172ce1F20EC0B3728c9965470eaf994A03557A'
|
||||||
|
const deployerAddress = '0xCEe71753C9820f063b38FDbE4cFDAf1d3D928A80'
|
||||||
|
const deploySalt = '0x0000000000000000000000000000000000000000000000000000000047941987'
|
||||||
|
const rpcUrl = 'https://mainnet.infura.io'
|
||||||
|
|
||||||
|
const Web3 = require('web3')
|
||||||
|
const web3 = new Web3(rpcUrl)
|
||||||
|
|
||||||
|
const contractData = require('./build/contracts/' + (eth ? 'ETHTornado.json' : 'ERC20Tornado.json'))
|
||||||
|
const contract = new web3.eth.Contract(contractData.abi)
|
||||||
|
const bytes = contract
|
||||||
|
.deploy({
|
||||||
|
data: contractData.bytecode,
|
||||||
|
arguments: [verifierAddress, hasherAddress, poolSize, 20],
|
||||||
|
})
|
||||||
|
.encodeABI()
|
||||||
|
|
||||||
|
console.log('Deploy bytecode', bytes)
|
||||||
|
|
||||||
|
const deployer = new web3.eth.Contract(require('./build/contracts/IDeployer.json').abi, deployerAddress)
|
||||||
|
const receipt = deployer.methods.deploy(bytes, deploySalt)
|
||||||
|
receipt.then(console.log).catch(console.log)
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Binary file not shown.
After Width: | Height: | Size: 205 KiB |
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
29
index.html
29
index.html
|
@ -1,16 +1,17 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<title>Tornado test</title>
|
<title>Tornado test</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>
|
<p>
|
||||||
Open dev console!<br>
|
Open dev console!<br />
|
||||||
Make sure your Metamask is unlocked and connected to Kovan (or other network you've deployed your contract to)<br>
|
Make sure your Metamask is unlocked and connected to Kovan (or other network you've deployed your
|
||||||
<a href="#" onclick="deposit()">Deposit</a>
|
contract to)<br />
|
||||||
<a href="#" onclick="withdraw()">Withdraw</a>
|
<a href="#" onclick="deposit()">Deposit</a>
|
||||||
</p>
|
<a href="#" onclick="withdraw()">Withdraw</a>
|
||||||
<script src="index.js"></script>
|
</p>
|
||||||
</body>
|
<script src="index.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,195 +0,0 @@
|
||||||
const jsStorage = require('./Storage')
|
|
||||||
const hasherImpl = require('./MiMC')
|
|
||||||
|
|
||||||
class MerkleTree {
|
|
||||||
|
|
||||||
constructor(n_levels, defaultElements, prefix, storage, hasher) {
|
|
||||||
this.prefix = prefix
|
|
||||||
this.storage = storage || new jsStorage()
|
|
||||||
this.hasher = hasher || new hasherImpl()
|
|
||||||
this.n_levels = n_levels
|
|
||||||
this.zero_values = []
|
|
||||||
this.totalElements = 0
|
|
||||||
|
|
||||||
let current_zero_value = '21663839004416932945382355908790599225266501822907911457504978515578255421292'
|
|
||||||
this.zero_values.push(current_zero_value)
|
|
||||||
for (let i = 0; i < n_levels; i++) {
|
|
||||||
current_zero_value = this.hasher.hash(i, current_zero_value, current_zero_value)
|
|
||||||
this.zero_values.push(
|
|
||||||
current_zero_value.toString(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (defaultElements) {
|
|
||||||
let level = 0
|
|
||||||
this.totalElements = defaultElements.length
|
|
||||||
defaultElements.forEach((element, i) => {
|
|
||||||
this.storage.put(MerkleTree.index_to_key(prefix, level, i), element)
|
|
||||||
})
|
|
||||||
level++
|
|
||||||
let numberOfElementsInLevel = Math.ceil(defaultElements.length / 2)
|
|
||||||
for (level; level <= this.n_levels; level++) {
|
|
||||||
for(let i = 0; i < numberOfElementsInLevel; i++) {
|
|
||||||
const leftKey = MerkleTree.index_to_key(prefix, level - 1, 2 * i)
|
|
||||||
const rightKey = MerkleTree.index_to_key(prefix, level - 1, 2 * i + 1)
|
|
||||||
|
|
||||||
const left = this.storage.get(leftKey)
|
|
||||||
const right = this.storage.get_or_element(rightKey, this.zero_values[level - 1])
|
|
||||||
|
|
||||||
const subRoot = this.hasher.hash(null, left, right)
|
|
||||||
this.storage.put(MerkleTree.index_to_key(prefix, level, i), subRoot)
|
|
||||||
}
|
|
||||||
numberOfElementsInLevel = Math.ceil(numberOfElementsInLevel / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static index_to_key(prefix, level, index) {
|
|
||||||
const key = `${prefix}_tree_${level}_${index}`
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
async root() {
|
|
||||||
let root = await this.storage.get_or_element(
|
|
||||||
MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
|
|
||||||
this.zero_values[this.n_levels],
|
|
||||||
)
|
|
||||||
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
async path(index) {
|
|
||||||
class PathTraverser {
|
|
||||||
constructor(prefix, storage, zero_values) {
|
|
||||||
this.prefix = prefix
|
|
||||||
this.storage = storage
|
|
||||||
this.zero_values = zero_values
|
|
||||||
this.path_elements = []
|
|
||||||
this.path_index = []
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle_index(level, element_index, sibling_index) {
|
|
||||||
const sibling = await this.storage.get_or_element(
|
|
||||||
MerkleTree.index_to_key(this.prefix, level, sibling_index),
|
|
||||||
this.zero_values[level],
|
|
||||||
)
|
|
||||||
this.path_elements.push(sibling)
|
|
||||||
this.path_index.push(element_index % 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
index = Number(index)
|
|
||||||
let traverser = new PathTraverser(this.prefix, this.storage, this.zero_values)
|
|
||||||
const root = await this.storage.get_or_element(
|
|
||||||
MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
|
|
||||||
this.zero_values[this.n_levels],
|
|
||||||
)
|
|
||||||
|
|
||||||
const element = await this.storage.get_or_element(
|
|
||||||
MerkleTree.index_to_key(this.prefix, 0, index),
|
|
||||||
this.zero_values[0],
|
|
||||||
)
|
|
||||||
|
|
||||||
await this.traverse(index, traverser)
|
|
||||||
return {
|
|
||||||
root,
|
|
||||||
path_elements: traverser.path_elements,
|
|
||||||
path_index: traverser.path_index,
|
|
||||||
element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(index, element, insert = false) {
|
|
||||||
if (!insert && index >= this.totalElements) {
|
|
||||||
throw Error('Use insert method for new elements.')
|
|
||||||
} else if(insert && index < this.totalElements) {
|
|
||||||
throw Error('Use update method for existing elements.')
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
class UpdateTraverser {
|
|
||||||
constructor(prefix, storage, hasher, element, zero_values) {
|
|
||||||
this.prefix = prefix
|
|
||||||
this.current_element = element
|
|
||||||
this.zero_values = zero_values
|
|
||||||
this.storage = storage
|
|
||||||
this.hasher = hasher
|
|
||||||
this.key_values_to_put = []
|
|
||||||
}
|
|
||||||
|
|
||||||
async handle_index(level, element_index, sibling_index) {
|
|
||||||
if (level == 0) {
|
|
||||||
this.original_element = await this.storage.get_or_element(
|
|
||||||
MerkleTree.index_to_key(this.prefix, level, element_index),
|
|
||||||
this.zero_values[level],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const sibling = await this.storage.get_or_element(
|
|
||||||
MerkleTree.index_to_key(this.prefix, level, sibling_index),
|
|
||||||
this.zero_values[level],
|
|
||||||
)
|
|
||||||
let left, right
|
|
||||||
if (element_index % 2 == 0) {
|
|
||||||
left = this.current_element
|
|
||||||
right = sibling
|
|
||||||
} else {
|
|
||||||
left = sibling
|
|
||||||
right = this.current_element
|
|
||||||
}
|
|
||||||
|
|
||||||
this.key_values_to_put.push({
|
|
||||||
key: MerkleTree.index_to_key(this.prefix, level, element_index),
|
|
||||||
value: this.current_element,
|
|
||||||
})
|
|
||||||
this.current_element = this.hasher.hash(level, left, right)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let traverser = new UpdateTraverser(
|
|
||||||
this.prefix,
|
|
||||||
this.storage,
|
|
||||||
this.hasher,
|
|
||||||
element,
|
|
||||||
this.zero_values
|
|
||||||
)
|
|
||||||
|
|
||||||
await this.traverse(index, traverser)
|
|
||||||
traverser.key_values_to_put.push({
|
|
||||||
key: MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
|
|
||||||
value: traverser.current_element,
|
|
||||||
})
|
|
||||||
|
|
||||||
await this.storage.put_batch(traverser.key_values_to_put)
|
|
||||||
} catch(e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async insert(element) {
|
|
||||||
const index = this.totalElements
|
|
||||||
await this.update(index, element, true)
|
|
||||||
this.totalElements++
|
|
||||||
}
|
|
||||||
|
|
||||||
async traverse(index, handler) {
|
|
||||||
let current_index = index
|
|
||||||
for (let i = 0; i < this.n_levels; i++) {
|
|
||||||
let sibling_index = current_index
|
|
||||||
if (current_index % 2 == 0) {
|
|
||||||
sibling_index += 1
|
|
||||||
} else {
|
|
||||||
sibling_index -= 1
|
|
||||||
}
|
|
||||||
await handler.handle_index(i, current_index, sibling_index)
|
|
||||||
current_index = Math.floor(current_index / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getIndexByElement(element) {
|
|
||||||
for(let i = this.totalElements - 1; i >= 0; i--) {
|
|
||||||
const elementFromTree = this.storage.get(MerkleTree.index_to_key(this.prefix, 0, i))
|
|
||||||
if (elementFromTree === element) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MerkleTree
|
|
13
lib/MiMC.js
13
lib/MiMC.js
|
@ -1,13 +0,0 @@
|
||||||
const circomlib = require('circomlib')
|
|
||||||
const mimcsponge = circomlib.mimcsponge
|
|
||||||
const snarkjs = require('snarkjs')
|
|
||||||
|
|
||||||
const bigInt = snarkjs.bigInt
|
|
||||||
|
|
||||||
class MimcSpongeHasher {
|
|
||||||
hash(level, left, right) {
|
|
||||||
return mimcsponge.multiHash([bigInt(left), bigInt(right)]).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MimcSpongeHasher
|
|
|
@ -1,39 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
class JsStorage {
|
|
||||||
constructor() {
|
|
||||||
this.db = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key) {
|
|
||||||
return this.db[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
get_or_element(key, defaultElement) {
|
|
||||||
const element = this.db[key]
|
|
||||||
if (element === undefined) {
|
|
||||||
return defaultElement
|
|
||||||
} else {
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
put(key, value) {
|
|
||||||
if (key === undefined || value === undefined) {
|
|
||||||
throw Error('key or value is undefined')
|
|
||||||
}
|
|
||||||
this.db[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
del(key) {
|
|
||||||
delete this.db[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
put_batch(key_values) {
|
|
||||||
key_values.forEach(element => {
|
|
||||||
this.db[element.key] = element.value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = JsStorage
|
|
|
@ -1,9 +0,0 @@
|
||||||
/* global artifacts */
|
|
||||||
const Migrations = artifacts.require('Migrations')
|
|
||||||
|
|
||||||
module.exports = function(deployer) {
|
|
||||||
if(deployer.network === 'mainnet') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
deployer.deploy(Migrations)
|
|
||||||
}
|
|
|
@ -1,21 +1,6 @@
|
||||||
/* global artifacts */
|
/* global artifacts */
|
||||||
const path = require('path')
|
const Hasher = artifacts.require('Hasher')
|
||||||
|
|
||||||
const genContract = require('circomlib/src/mimcsponge_gencontract.js')
|
module.exports = async function (deployer) {
|
||||||
const Artifactor = require('@truffle/artifactor')
|
await deployer.deploy(Hasher)
|
||||||
|
|
||||||
module.exports = function(deployer) {
|
|
||||||
return deployer.then( async () => {
|
|
||||||
const contractsDir = path.join(__dirname, '..', 'build/contracts')
|
|
||||||
let artifactor = new Artifactor(contractsDir)
|
|
||||||
let contractName = 'Hasher'
|
|
||||||
await artifactor.save({
|
|
||||||
contractName,
|
|
||||||
abi: genContract.abi,
|
|
||||||
unlinked_binary: genContract.createCode('mimcsponge', 220),
|
|
||||||
}).then(async () => {
|
|
||||||
const hasherContract = artifacts.require(contractName)
|
|
||||||
await deployer.deploy(hasherContract)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* global artifacts */
|
/* global artifacts */
|
||||||
const Verifier = artifacts.require('Verifier')
|
const Verifier = artifacts.require('Verifier')
|
||||||
|
|
||||||
module.exports = function(deployer) {
|
module.exports = function (deployer) {
|
||||||
deployer.deploy(Verifier)
|
deployer.deploy(Verifier)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,20 @@
|
||||||
require('dotenv').config({ path: '../.env' })
|
require('dotenv').config({ path: '../.env' })
|
||||||
const ETHTornado = artifacts.require('ETHTornado')
|
const ETHTornado = artifacts.require('ETHTornado')
|
||||||
const Verifier = artifacts.require('Verifier')
|
const Verifier = artifacts.require('Verifier')
|
||||||
const hasherContract = artifacts.require('Hasher')
|
const Hasher = artifacts.require('Hasher')
|
||||||
|
|
||||||
|
module.exports = function (deployer) {
|
||||||
module.exports = function(deployer, network, accounts) {
|
|
||||||
return deployer.then(async () => {
|
return deployer.then(async () => {
|
||||||
const { MERKLE_TREE_HEIGHT, ETH_AMOUNT } = process.env
|
const { MERKLE_TREE_HEIGHT, ETH_AMOUNT } = process.env
|
||||||
const verifier = await Verifier.deployed()
|
const verifier = await Verifier.deployed()
|
||||||
const hasherInstance = await hasherContract.deployed()
|
const hasher = await Hasher.deployed()
|
||||||
await ETHTornado.link(hasherContract, hasherInstance.address)
|
const tornado = await deployer.deploy(
|
||||||
const tornado = await deployer.deploy(ETHTornado, verifier.address, ETH_AMOUNT, MERKLE_TREE_HEIGHT, accounts[0])
|
ETHTornado,
|
||||||
console.log('ETHTornado\'s address ', tornado.address)
|
verifier.address,
|
||||||
|
hasher.address,
|
||||||
|
ETH_AMOUNT,
|
||||||
|
MERKLE_TREE_HEIGHT,
|
||||||
|
)
|
||||||
|
console.log('ETHTornado address', tornado.address)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,29 +2,27 @@
|
||||||
require('dotenv').config({ path: '../.env' })
|
require('dotenv').config({ path: '../.env' })
|
||||||
const ERC20Tornado = artifacts.require('ERC20Tornado')
|
const ERC20Tornado = artifacts.require('ERC20Tornado')
|
||||||
const Verifier = artifacts.require('Verifier')
|
const Verifier = artifacts.require('Verifier')
|
||||||
const hasherContract = artifacts.require('Hasher')
|
const Hasher = artifacts.require('Hasher')
|
||||||
const ERC20Mock = artifacts.require('ERC20Mock')
|
const ERC20Mock = artifacts.require('ERC20Mock')
|
||||||
|
|
||||||
|
module.exports = function (deployer) {
|
||||||
module.exports = function(deployer, network, accounts) {
|
|
||||||
return deployer.then(async () => {
|
return deployer.then(async () => {
|
||||||
const { MERKLE_TREE_HEIGHT, ERC20_TOKEN, TOKEN_AMOUNT } = process.env
|
const { MERKLE_TREE_HEIGHT, ERC20_TOKEN, TOKEN_AMOUNT } = process.env
|
||||||
const verifier = await Verifier.deployed()
|
const verifier = await Verifier.deployed()
|
||||||
const hasherInstance = await hasherContract.deployed()
|
const hasher = await Hasher.deployed()
|
||||||
await ERC20Tornado.link(hasherContract, hasherInstance.address)
|
|
||||||
let token = ERC20_TOKEN
|
let token = ERC20_TOKEN
|
||||||
if(token === '') {
|
if (token === '') {
|
||||||
const tokenInstance = await deployer.deploy(ERC20Mock)
|
const tokenInstance = await deployer.deploy(ERC20Mock)
|
||||||
token = tokenInstance.address
|
token = tokenInstance.address
|
||||||
}
|
}
|
||||||
const tornado = await deployer.deploy(
|
const tornado = await deployer.deploy(
|
||||||
ERC20Tornado,
|
ERC20Tornado,
|
||||||
verifier.address,
|
verifier.address,
|
||||||
|
hasher.address,
|
||||||
TOKEN_AMOUNT,
|
TOKEN_AMOUNT,
|
||||||
MERKLE_TREE_HEIGHT,
|
MERKLE_TREE_HEIGHT,
|
||||||
accounts[0],
|
|
||||||
token,
|
token,
|
||||||
)
|
)
|
||||||
console.log('ERC20Tornado\'s address ', tornado.address)
|
console.log('ERC20Tornado address', tornado.address)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
|
@ -10,7 +10,7 @@
|
||||||
"build:circuit:contract": "npx snarkjs generateverifier -v build/circuits/Verifier.sol --vk build/circuits/withdraw_verification_key.json",
|
"build:circuit:contract": "npx snarkjs generateverifier -v build/circuits/Verifier.sol --vk build/circuits/withdraw_verification_key.json",
|
||||||
"build:circuit": "mkdir -p build/circuits && npm run build:circuit:compile && npm run build:circuit:setup && npm run build:circuit:bin && npm run build:circuit:contract",
|
"build:circuit": "mkdir -p build/circuits && npm run build:circuit:compile && npm run build:circuit:setup && npm run build:circuit:bin && npm run build:circuit:contract",
|
||||||
"build:contract": "npx truffle compile",
|
"build:contract": "npx truffle compile",
|
||||||
"build:browserify": "npx browserify cli.js -o index.js --exclude worker_threads",
|
"build:browserify": "npx browserify src/cli.js -o index.js --exclude worker_threads",
|
||||||
"build": "npm run build:circuit && npm run build:contract && npm run build:browserify",
|
"build": "npm run build:circuit && npm run build:contract && npm run build:browserify",
|
||||||
"browserify": "npm run build:browserify",
|
"browserify": "npm run build:browserify",
|
||||||
"test": "npx truffle test",
|
"test": "npx truffle test",
|
||||||
|
@ -19,19 +19,23 @@
|
||||||
"migrate:kovan": "npx truffle migrate --network kovan --reset",
|
"migrate:kovan": "npx truffle migrate --network kovan --reset",
|
||||||
"migrate:rinkeby": "npx truffle migrate --network rinkeby --reset",
|
"migrate:rinkeby": "npx truffle migrate --network rinkeby --reset",
|
||||||
"migrate:mainnet": "npx truffle migrate --network mainnet",
|
"migrate:mainnet": "npx truffle migrate --network mainnet",
|
||||||
"eslint": "npx eslint --ignore-path .gitignore .",
|
"eslint": "eslint --ext .js --ignore-path .gitignore .",
|
||||||
|
"prettier:check": "prettier --check . --config .prettierrc",
|
||||||
|
"prettier:fix": "prettier --write . --config .prettierrc",
|
||||||
|
"lint": "yarn eslint && yarn prettier:check",
|
||||||
"flat": "npx truffle-flattener contracts/ETHTornado.sol > ETHTornado_flat.sol && npx truffle-flattener contracts/ERC20Tornado.sol > ERC20Tornado_flat.sol",
|
"flat": "npx truffle-flattener contracts/ETHTornado.sol > ETHTornado_flat.sol && npx truffle-flattener contracts/ERC20Tornado.sol > ERC20Tornado_flat.sol",
|
||||||
"download": "node downloadKeys.js"
|
"download": "node scripts/downloadKeys.js",
|
||||||
|
"coverage": "yarn truffle run coverage"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openzeppelin/contracts": "^2.4.0",
|
"@openzeppelin/contracts": "^3.4.1",
|
||||||
"@truffle/artifactor": "^4.0.38",
|
|
||||||
"@truffle/contract": "^4.0.39",
|
"@truffle/contract": "^4.0.39",
|
||||||
"@truffle/hdwallet-provider": "^1.0.24",
|
"@truffle/hdwallet-provider": "^1.0.24",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
|
"babel-eslint": "^10.1.0",
|
||||||
"bn-chai": "^1.0.1",
|
"bn-chai": "^1.0.1",
|
||||||
"browserify": "^16.5.0",
|
"browserify": "^16.5.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
|
@ -40,13 +44,21 @@
|
||||||
"circomlib": "git+https://github.com/tornadocash/circomlib.git#c372f14d324d57339c88451834bf2824e73bbdbc",
|
"circomlib": "git+https://github.com/tornadocash/circomlib.git#c372f14d324d57339c88451834bf2824e73bbdbc",
|
||||||
"commander": "^4.1.1",
|
"commander": "^4.1.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"eslint": "^6.6.0",
|
"eslint": "^7.19.0",
|
||||||
|
"eslint-config-prettier": "^7.2.0",
|
||||||
|
"eslint-plugin-prettier": "^3.3.1",
|
||||||
|
"eth-json-rpc-filters": "^4.1.1",
|
||||||
|
"fixed-merkle-tree": "^0.6.0",
|
||||||
"ganache-cli": "^6.7.0",
|
"ganache-cli": "^6.7.0",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
|
"prettier-plugin-solidity": "^1.0.0-beta.3",
|
||||||
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5",
|
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5",
|
||||||
"truffle": "^5.0.44",
|
"solhint-plugin-prettier": "^0.0.5",
|
||||||
|
"truffle": "^5.1.67",
|
||||||
"truffle-flattener": "^1.4.2",
|
"truffle-flattener": "^1.4.2",
|
||||||
"web3": "^1.2.2",
|
"web3": "^1.3.4",
|
||||||
"web3-utils": "^1.2.2",
|
"web3-utils": "^1.3.4",
|
||||||
"websnark": "git+https://github.com/tornadocash/websnark.git#2041cfa5fa0b71cd5cca9022a4eeea4afe28c9f7"
|
"websnark": "git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d",
|
||||||
|
"solidity-coverage": "^0.7.20"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Generates Hasher artifact at compile-time using Truffle's external compiler
|
||||||
|
// mechanism
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
const genContract = require('circomlib/src/mimcsponge_gencontract.js')
|
||||||
|
|
||||||
|
// where Truffle will expect to find the results of the external compiler
|
||||||
|
// command
|
||||||
|
const outputPath = path.join(__dirname, '..', 'build', 'Hasher.json')
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const contract = {
|
||||||
|
contractName: 'Hasher',
|
||||||
|
abi: genContract.abi,
|
||||||
|
bytecode: genContract.createCode('mimcsponge', 220),
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, JSON.stringify(contract))
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
|
@ -2,8 +2,8 @@ const axios = require('axios')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const files = ['withdraw.json', 'withdraw_proving_key.bin', 'Verifier.sol', 'withdraw_verification_key.json']
|
const files = ['withdraw.json', 'withdraw_proving_key.bin', 'Verifier.sol', 'withdraw_verification_key.json']
|
||||||
const circuitsPath = 'build/circuits'
|
const circuitsPath = __dirname + '/../build/circuits'
|
||||||
const contractsPath = 'build/contracts'
|
const contractsPath = __dirname + '/../build/contracts'
|
||||||
|
|
||||||
async function downloadFile({ url, path }) {
|
async function downloadFile({ url, path }) {
|
||||||
const writer = fs.createWriteStream(path)
|
const writer = fs.createWriteStream(path)
|
||||||
|
@ -11,7 +11,7 @@ async function downloadFile({ url, path }) {
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
url,
|
url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
responseType: 'stream'
|
responseType: 'stream',
|
||||||
})
|
})
|
||||||
|
|
||||||
response.data.pipe(writer)
|
response.data.pipe(writer)
|
||||||
|
@ -25,16 +25,16 @@ async function downloadFile({ url, path }) {
|
||||||
async function main() {
|
async function main() {
|
||||||
const release = await axios.get('https://api.github.com/repos/tornadocash/tornado-core/releases/latest')
|
const release = await axios.get('https://api.github.com/repos/tornadocash/tornado-core/releases/latest')
|
||||||
const { assets } = release.data
|
const { assets } = release.data
|
||||||
if (!fs.existsSync(circuitsPath)){
|
if (!fs.existsSync(circuitsPath)) {
|
||||||
fs.mkdirSync(circuitsPath, { recursive: true })
|
fs.mkdirSync(circuitsPath, { recursive: true })
|
||||||
fs.mkdirSync(contractsPath, { recursive: true })
|
fs.mkdirSync(contractsPath, { recursive: true })
|
||||||
}
|
}
|
||||||
for(let asset of assets) {
|
for (let asset of assets) {
|
||||||
if (files.includes(asset.name)) {
|
if (files.includes(asset.name)) {
|
||||||
console.log(`Downloading ${asset.name} ...`)
|
console.log(`Downloading ${asset.name} ...`)
|
||||||
await downloadFile({
|
await downloadFile({
|
||||||
url: asset.browser_download_url,
|
url: asset.browser_download_url,
|
||||||
path: path.resolve(__dirname, circuitsPath, asset.name)
|
path: path.resolve(__dirname, circuitsPath, asset.name),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,7 +6,7 @@ function send(method, params = []) {
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
method,
|
method,
|
||||||
params
|
params,
|
||||||
}, (err, res) => {
|
}, (err, res) => {
|
||||||
return err ? reject(err) : resolve(res)
|
return err ? reject(err) : resolve(res)
|
||||||
})
|
})
|
||||||
|
@ -48,5 +48,5 @@ module.exports = {
|
||||||
minerStop,
|
minerStop,
|
||||||
minerStart,
|
minerStart,
|
||||||
increaseTime,
|
increaseTime,
|
||||||
traceTransaction
|
traceTransaction,
|
||||||
}
|
}
|
|
@ -10,7 +10,7 @@ const snarkjs = require('snarkjs')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const circomlib = require('circomlib')
|
const circomlib = require('circomlib')
|
||||||
const bigInt = snarkjs.bigInt
|
const bigInt = snarkjs.bigInt
|
||||||
const merkleTree = require('./lib/MerkleTree')
|
const merkleTree = require('fixed-merkle-tree')
|
||||||
const Web3 = require('web3')
|
const Web3 = require('web3')
|
||||||
const buildGroth16 = require('websnark/src/groth16')
|
const buildGroth16 = require('websnark/src/groth16')
|
||||||
const websnarkUtils = require('websnark/src/utils')
|
const websnarkUtils = require('websnark/src/utils')
|
||||||
|
@ -24,7 +24,6 @@ let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, PRIVATE_KEY
|
||||||
/** Whether we are in a browser or node.js */
|
/** Whether we are in a browser or node.js */
|
||||||
const inBrowser = (typeof window !== 'undefined')
|
const inBrowser = (typeof window !== 'undefined')
|
||||||
let isLocalRPC = false
|
let isLocalRPC = false
|
||||||
const networks = { '1': 'mainnet', '42': 'kovan' }
|
|
||||||
|
|
||||||
/** Generate random number of specified byte length */
|
/** Generate random number of specified byte length */
|
||||||
const rbigint = nbytes => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
const rbigint = nbytes => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||||
|
@ -45,7 +44,7 @@ async function printETHBalance({ address, name }) {
|
||||||
|
|
||||||
/** Display ERC20 account balance */
|
/** Display ERC20 account balance */
|
||||||
async function printERC20Balance({ address, name, tokenAddress }) {
|
async function printERC20Balance({ address, name, tokenAddress }) {
|
||||||
const erc20ContractJson = require('./build/contracts/ERC20Mock.json')
|
const erc20ContractJson = require(__dirname + '/../build/contracts/ERC20Mock.json')
|
||||||
erc20 = tokenAddress ? new web3.eth.Contract(erc20ContractJson.abi, tokenAddress) : erc20
|
erc20 = tokenAddress ? new web3.eth.Contract(erc20ContractJson.abi, tokenAddress) : erc20
|
||||||
console.log(`${name} Token Balance is`, web3.utils.fromWei(await erc20.methods.balanceOf(address).call()))
|
console.log(`${name} Token Balance is`, web3.utils.fromWei(await erc20.methods.balanceOf(address).call()))
|
||||||
}
|
}
|
||||||
|
@ -57,7 +56,9 @@ function createDeposit({ nullifier, secret }) {
|
||||||
const deposit = { nullifier, secret }
|
const deposit = { nullifier, secret }
|
||||||
deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
|
deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
|
||||||
deposit.commitment = pedersenHash(deposit.preimage)
|
deposit.commitment = pedersenHash(deposit.preimage)
|
||||||
|
deposit.commitmentHex = toHex(deposit.commitment)
|
||||||
deposit.nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
|
deposit.nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
|
||||||
|
deposit.nullifierHex = toHex(deposit.nullifierHash)
|
||||||
return deposit
|
return deposit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ async function deposit({ currency, amount }) {
|
||||||
await printETHBalance({ address: senderAccount, name: 'Sender account' })
|
await printETHBalance({ address: senderAccount, name: 'Sender account' })
|
||||||
const value = isLocalRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 })
|
const value = isLocalRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 })
|
||||||
console.log('Submitting deposit transaction')
|
console.log('Submitting deposit transaction')
|
||||||
await tornado.methods.deposit(toHex(deposit.commitment)).send({ value, from: senderAccount, gas:2e6 })
|
await tornado.methods.deposit(toHex(deposit.commitment)).send({ value, from: senderAccount, gas: 2e6 })
|
||||||
await printETHBalance({ address: tornado._address, name: 'Tornado' })
|
await printETHBalance({ address: tornado._address, name: 'Tornado' })
|
||||||
await printETHBalance({ address: senderAccount, name: 'Sender account' })
|
await printETHBalance({ address: senderAccount, name: 'Sender account' })
|
||||||
} else { // a token
|
} else { // a token
|
||||||
|
@ -84,7 +85,7 @@ async function deposit({ currency, amount }) {
|
||||||
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
|
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
|
||||||
const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
|
const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
|
||||||
const tokenAmount = isLocalRPC ? TOKEN_AMOUNT : fromDecimals({ amount, decimals })
|
const tokenAmount = isLocalRPC ? TOKEN_AMOUNT : fromDecimals({ amount, decimals })
|
||||||
if(isLocalRPC) {
|
if (isLocalRPC) {
|
||||||
console.log('Minting some test tokens to deposit')
|
console.log('Minting some test tokens to deposit')
|
||||||
await erc20.methods.mint(senderAccount, tokenAmount).send({ from: senderAccount, gas: 2e6 })
|
await erc20.methods.mint(senderAccount, tokenAmount).send({ from: senderAccount, gas: 2e6 })
|
||||||
}
|
}
|
||||||
|
@ -93,11 +94,11 @@ async function deposit({ currency, amount }) {
|
||||||
console.log('Current allowance is', fromWei(allowance))
|
console.log('Current allowance is', fromWei(allowance))
|
||||||
if (toBN(allowance).lt(toBN(tokenAmount))) {
|
if (toBN(allowance).lt(toBN(tokenAmount))) {
|
||||||
console.log('Approving tokens for deposit')
|
console.log('Approving tokens for deposit')
|
||||||
await erc20.methods.approve(tornado._address, tokenAmount).send({ from: senderAccount, gas:1e6 })
|
await erc20.methods.approve(tornado._address, tokenAmount).send({ from: senderAccount, gas: 1e6 })
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Submitting deposit transaction')
|
console.log('Submitting deposit transaction')
|
||||||
await tornado.methods.deposit(toHex(deposit.commitment)).send({ from: senderAccount, gas:2e6 })
|
await tornado.methods.deposit(toHex(deposit.commitment)).send({ from: senderAccount, gas: 2e6 })
|
||||||
await printERC20Balance({ address: tornado._address, name: 'Tornado' })
|
await printERC20Balance({ address: tornado._address, name: 'Tornado' })
|
||||||
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
|
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
|
||||||
}
|
}
|
||||||
|
@ -125,14 +126,16 @@ async function generateMerkleProof(deposit) {
|
||||||
const leafIndex = depositEvent ? depositEvent.returnValues.leafIndex : -1
|
const leafIndex = depositEvent ? depositEvent.returnValues.leafIndex : -1
|
||||||
|
|
||||||
// Validate that our data is correct
|
// Validate that our data is correct
|
||||||
const isValidRoot = await tornado.methods.isKnownRoot(toHex(await tree.root())).call()
|
const root = tree.root()
|
||||||
|
const isValidRoot = await tornado.methods.isKnownRoot(toHex(root)).call()
|
||||||
const isSpent = await tornado.methods.isSpent(toHex(deposit.nullifierHash)).call()
|
const isSpent = await tornado.methods.isSpent(toHex(deposit.nullifierHash)).call()
|
||||||
assert(isValidRoot === true, 'Merkle tree is corrupted')
|
assert(isValidRoot === true, 'Merkle tree is corrupted')
|
||||||
assert(isSpent === false, 'The note is already spent')
|
assert(isSpent === false, 'The note is already spent')
|
||||||
assert(leafIndex >= 0, 'The deposit is not found in the tree')
|
assert(leafIndex >= 0, 'The deposit is not found in the tree')
|
||||||
|
|
||||||
// Compute merkle proof of our commitment
|
// Compute merkle proof of our commitment
|
||||||
return tree.path(leafIndex)
|
const { pathElements, pathIndices } = tree.path(leafIndex)
|
||||||
|
return { pathElements, pathIndices, root: tree.root() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,7 +148,7 @@ async function generateMerkleProof(deposit) {
|
||||||
*/
|
*/
|
||||||
async function generateProof({ deposit, recipient, relayerAddress = 0, fee = 0, refund = 0 }) {
|
async function generateProof({ deposit, recipient, relayerAddress = 0, fee = 0, refund = 0 }) {
|
||||||
// Compute merkle proof of our commitment
|
// Compute merkle proof of our commitment
|
||||||
const { root, path_elements, path_index } = await generateMerkleProof(deposit)
|
const { root, pathElements, pathIndices } = await generateMerkleProof(deposit)
|
||||||
|
|
||||||
// Prepare circuit input
|
// Prepare circuit input
|
||||||
const input = {
|
const input = {
|
||||||
|
@ -160,8 +163,8 @@ async function generateProof({ deposit, recipient, relayerAddress = 0, fee = 0,
|
||||||
// Private snark inputs
|
// Private snark inputs
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Generating SNARK proof')
|
console.log('Generating SNARK proof')
|
||||||
|
@ -176,7 +179,7 @@ async function generateProof({ deposit, recipient, relayerAddress = 0, fee = 0,
|
||||||
toHex(input.recipient, 20),
|
toHex(input.recipient, 20),
|
||||||
toHex(input.relayer, 20),
|
toHex(input.relayer, 20),
|
||||||
toHex(input.fee),
|
toHex(input.fee),
|
||||||
toHex(input.refund)
|
toHex(input.refund),
|
||||||
]
|
]
|
||||||
|
|
||||||
return { proof, args }
|
return { proof, args }
|
||||||
|
@ -193,6 +196,9 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
||||||
}
|
}
|
||||||
refund = toWei(refund)
|
refund = toWei(refund)
|
||||||
if (relayerURL) {
|
if (relayerURL) {
|
||||||
|
if (relayerURL.endsWith('.eth')) {
|
||||||
|
throw new Error('ENS name resolving is not supported. Please provide DNS name of the relayer. See instuctions in README.md')
|
||||||
|
}
|
||||||
const relayerStatus = await axios.get(relayerURL + '/status')
|
const relayerStatus = await axios.get(relayerURL + '/status')
|
||||||
const { relayerAddress, netId, gasPrices, ethPrices, relayerServiceFee } = relayerStatus.data
|
const { relayerAddress, netId, gasPrices, ethPrices, relayerServiceFee } = relayerStatus.data
|
||||||
assert(netId === await web3.eth.net.getId() || netId === '*', 'This relay is for different network')
|
assert(netId === await web3.eth.net.getId() || netId === '*', 'This relay is for different network')
|
||||||
|
@ -206,17 +212,17 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
||||||
const { proof, args } = await generateProof({ deposit, recipient, relayerAddress, fee, refund })
|
const { proof, args } = await generateProof({ deposit, recipient, relayerAddress, fee, refund })
|
||||||
|
|
||||||
console.log('Sending withdraw transaction through relay')
|
console.log('Sending withdraw transaction through relay')
|
||||||
try{
|
try {
|
||||||
const relay = await axios.post(relayerURL + '/relay', { contract: tornado._address, proof, args })
|
const relay = await axios.post(relayerURL + '/relay', { contract: tornado._address, proof, args })
|
||||||
if (netId === 1 || netId === 42) {
|
if (netId === 1 || netId === 42) {
|
||||||
console.log(`Transaction submitted through the relay. View transaction on etherscan https://${networks[netId]}.etherscan.io/tx/${relay.data.txHash}`)
|
console.log(`Transaction submitted through the relay. View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${relay.data.txHash}`)
|
||||||
} else {
|
} else {
|
||||||
console.log(`Transaction submitted through the relay. The transaction hash is ${relay.data.txHash}`)
|
console.log(`Transaction submitted through the relay. The transaction hash is ${relay.data.txHash}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const receipt = await waitForTxReceipt({ txHash: relay.data.txHash })
|
const receipt = await waitForTxReceipt({ txHash: relay.data.txHash })
|
||||||
console.log('Transaction mined in block', receipt.blockNumber)
|
console.log('Transaction mined in block', receipt.blockNumber)
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
if (e.response) {
|
if (e.response) {
|
||||||
console.error(e.response.data.error)
|
console.error(e.response.data.error)
|
||||||
} else {
|
} else {
|
||||||
|
@ -228,13 +234,13 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
||||||
|
|
||||||
console.log('Submitting withdraw transaction')
|
console.log('Submitting withdraw transaction')
|
||||||
await tornado.methods.withdraw(proof, ...args).send({ from: senderAccount, value: refund.toString(), gas: 1e6 })
|
await tornado.methods.withdraw(proof, ...args).send({ from: senderAccount, value: refund.toString(), gas: 1e6 })
|
||||||
.on('transactionHash', function(txHash){
|
.on('transactionHash', function (txHash) {
|
||||||
if (netId === 1 || netId === 42) {
|
if (netId === 1 || netId === 42) {
|
||||||
console.log(`View transaction on etherscan https://${networks[netId]}.etherscan.io/tx/${txHash}`)
|
console.log(`View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`)
|
||||||
} else {
|
} else {
|
||||||
console.log(`The transaction hash is ${txHash}`)
|
console.log(`The transaction hash is ${txHash}`)
|
||||||
}
|
}
|
||||||
}).on('error', function(e){
|
}).on('error', function (e) {
|
||||||
console.error('on transactionHash error', e.message)
|
console.error('on transactionHash error', e.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -260,7 +266,7 @@ function fromDecimals({ amount, decimals }) {
|
||||||
const comps = ether.split('.')
|
const comps = ether.split('.')
|
||||||
if (comps.length > 2) {
|
if (comps.length > 2) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points'
|
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +281,7 @@ function fromDecimals({ amount, decimals }) {
|
||||||
}
|
}
|
||||||
if (fraction.length > baseLength) {
|
if (fraction.length > baseLength) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places'
|
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,8 +300,58 @@ function fromDecimals({ amount, decimals }) {
|
||||||
return new BN(wei.toString(10), 10)
|
return new BN(wei.toString(10), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toDecimals(value, decimals, fixed) {
|
||||||
|
const zero = new BN(0)
|
||||||
|
const negative1 = new BN(-1)
|
||||||
|
decimals = decimals || 18
|
||||||
|
fixed = fixed || 7
|
||||||
|
|
||||||
|
value = new BN(value)
|
||||||
|
const negative = value.lt(zero)
|
||||||
|
const base = new BN('10').pow(new BN(decimals))
|
||||||
|
const baseLength = base.toString(10).length - 1 || 1
|
||||||
|
|
||||||
|
if (negative) {
|
||||||
|
value = value.mul(negative1)
|
||||||
|
}
|
||||||
|
|
||||||
|
let fraction = value.mod(base).toString(10)
|
||||||
|
while (fraction.length < baseLength) {
|
||||||
|
fraction = `0${fraction}`
|
||||||
|
}
|
||||||
|
fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1]
|
||||||
|
|
||||||
|
const whole = value.div(base).toString(10)
|
||||||
|
value = `${whole}${fraction === '0' ? '' : `.${fraction}`}`
|
||||||
|
|
||||||
|
if (negative) {
|
||||||
|
value = `-${value}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fixed) {
|
||||||
|
value = value.slice(0, fixed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentNetworkName() {
|
||||||
|
switch (netId) {
|
||||||
|
case 1:
|
||||||
|
return ''
|
||||||
|
case 42:
|
||||||
|
return 'kovan.'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function calculateFee({ gasPrices, currency, amount, refund, ethPrices, relayerServiceFee, decimals }) {
|
function calculateFee({ gasPrices, currency, amount, refund, ethPrices, relayerServiceFee, decimals }) {
|
||||||
const feePercent = toBN(fromDecimals({ amount, decimals })).mul(toBN(relayerServiceFee * 10)).div(toBN('1000'))
|
const decimalsPoint = Math.floor(relayerServiceFee) === Number(relayerServiceFee) ?
|
||||||
|
0 :
|
||||||
|
relayerServiceFee.toString().split('.')[1].length
|
||||||
|
const roundDecimal = 10 ** decimalsPoint
|
||||||
|
const total = toBN(fromDecimals({ amount, decimals }))
|
||||||
|
const feePercent = total.mul(toBN(relayerServiceFee * roundDecimal)).div(toBN(roundDecimal * 100))
|
||||||
const expense = toBN(toWei(gasPrices.fast.toString(), 'gwei')).mul(toBN(5e5))
|
const expense = toBN(toWei(gasPrices.fast.toString(), 'gwei')).mul(toBN(5e5))
|
||||||
let desiredFee
|
let desiredFee
|
||||||
switch (currency) {
|
switch (currency) {
|
||||||
|
@ -304,10 +360,9 @@ function calculateFee({ gasPrices, currency, amount, refund, ethPrices, relayerS
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
desiredFee =
|
desiredFee = expense.add(toBN(refund))
|
||||||
expense.add(toBN(refund))
|
.mul(toBN(10 ** decimals))
|
||||||
.mul(toBN(10 ** decimals))
|
.div(toBN(ethPrices[currency]))
|
||||||
.div(toBN(ethPrices[currency]))
|
|
||||||
desiredFee = desiredFee.add(feePercent)
|
desiredFee = desiredFee.add(feePercent)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -359,6 +414,60 @@ function parseNote(noteString) {
|
||||||
return { currency: match.groups.currency, amount: match.groups.amount, netId, deposit }
|
return { currency: match.groups.currency, amount: match.groups.amount, netId, deposit }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadDepositData({ deposit }) {
|
||||||
|
try {
|
||||||
|
const eventWhenHappened = await tornado.getPastEvents('Deposit', {
|
||||||
|
filter: {
|
||||||
|
commitment: deposit.commitmentHex,
|
||||||
|
},
|
||||||
|
fromBlock: 0,
|
||||||
|
toBlock: 'latest',
|
||||||
|
})
|
||||||
|
if (eventWhenHappened.length === 0) {
|
||||||
|
throw new Error('There is no related deposit, the note is invalid')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { timestamp } = eventWhenHappened[0].returnValues
|
||||||
|
const txHash = eventWhenHappened[0].transactionHash
|
||||||
|
const isSpent = await tornado.methods.isSpent(deposit.nullifierHex).call()
|
||||||
|
const receipt = await web3.eth.getTransactionReceipt(txHash)
|
||||||
|
|
||||||
|
return { timestamp, txHash, isSpent, from: receipt.from, commitment: deposit.commitmentHex }
|
||||||
|
} catch (e) {
|
||||||
|
console.error('loadDepositData', e)
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
async function loadWithdrawalData({ amount, currency, deposit }) {
|
||||||
|
try {
|
||||||
|
const events = await tornado.getPastEvents('Withdrawal', {
|
||||||
|
fromBlock: 0,
|
||||||
|
toBlock: 'latest',
|
||||||
|
})
|
||||||
|
|
||||||
|
const withdrawEvent = events.filter((event) => {
|
||||||
|
return event.returnValues.nullifierHash === deposit.nullifierHex
|
||||||
|
})[0]
|
||||||
|
|
||||||
|
const fee = withdrawEvent.returnValues.fee
|
||||||
|
const decimals = config.deployments[`netId${netId}`][currency].decimals
|
||||||
|
const withdrawalAmount = toBN(fromDecimals({ amount, decimals })).sub(
|
||||||
|
toBN(fee),
|
||||||
|
)
|
||||||
|
const { timestamp } = await web3.eth.getBlock(withdrawEvent.blockHash)
|
||||||
|
return {
|
||||||
|
amount: toDecimals(withdrawalAmount, decimals, 9),
|
||||||
|
txHash: withdrawEvent.transactionHash,
|
||||||
|
to: withdrawEvent.returnValues.to,
|
||||||
|
timestamp,
|
||||||
|
nullifier: deposit.nullifierHex,
|
||||||
|
fee: toDecimals(fee, decimals, 9),
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('loadWithdrawalData', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init web3, contracts, and snark
|
* Init web3, contracts, and snark
|
||||||
*/
|
*/
|
||||||
|
@ -379,15 +488,23 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
|
||||||
} else {
|
} else {
|
||||||
// Initialize from local node
|
// Initialize from local node
|
||||||
web3 = new Web3(rpc, null, { transactionConfirmationBlocks: 1 })
|
web3 = new Web3(rpc, null, { transactionConfirmationBlocks: 1 })
|
||||||
contractJson = require('./build/contracts/ETHTornado.json')
|
contractJson = require(__dirname + '/../build/contracts/ETHTornado.json')
|
||||||
circuit = require('./build/circuits/withdraw.json')
|
circuit = require(__dirname + '/../build/circuits/withdraw.json')
|
||||||
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
proving_key = fs.readFileSync(__dirname + '/../build/circuits/withdraw_proving_key.bin').buffer
|
||||||
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT
|
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20
|
||||||
ETH_AMOUNT = process.env.ETH_AMOUNT
|
ETH_AMOUNT = process.env.ETH_AMOUNT
|
||||||
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT
|
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT
|
||||||
PRIVATE_KEY = process.env.PRIVATE_KEY
|
PRIVATE_KEY = process.env.PRIVATE_KEY
|
||||||
erc20ContractJson = require('./build/contracts/ERC20Mock.json')
|
if (PRIVATE_KEY) {
|
||||||
erc20tornadoJson = require('./build/contracts/ERC20Tornado.json')
|
const account = web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
|
||||||
|
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
|
||||||
|
web3.eth.defaultAccount = account.address
|
||||||
|
senderAccount = account.address
|
||||||
|
} else {
|
||||||
|
console.log('Warning! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit')
|
||||||
|
}
|
||||||
|
erc20ContractJson = require(__dirname + '/../build/contracts/ERC20Mock.json')
|
||||||
|
erc20tornadoJson = require(__dirname + '/../build/contracts/ERC20Tornado.json')
|
||||||
}
|
}
|
||||||
// groth16 initialises a lot of Promises that will never be resolved, that's why we need to use process.exit to terminate the CLI
|
// groth16 initialises a lot of Promises that will never be resolved, that's why we need to use process.exit to terminate the CLI
|
||||||
groth16 = await buildGroth16()
|
groth16 = await buildGroth16()
|
||||||
|
@ -403,22 +520,12 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
|
||||||
senderAccount = (await web3.eth.getAccounts())[0]
|
senderAccount = (await web3.eth.getAccounts())[0]
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const account = web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
|
|
||||||
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
|
|
||||||
// eslint-disable-next-line require-atomic-updates
|
|
||||||
web3.eth.defaultAccount = account.address
|
|
||||||
senderAccount = account.address
|
|
||||||
} catch(e) {
|
|
||||||
console.error('Please provide PRIVATE_KEY in .env file')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
try{
|
|
||||||
tornadoAddress = config.deployments[`netId${netId}`][currency].instanceAddress[amount]
|
tornadoAddress = config.deployments[`netId${netId}`][currency].instanceAddress[amount]
|
||||||
if (!tornadoAddress) {
|
if (!tornadoAddress) {
|
||||||
throw new Error()
|
throw new Error()
|
||||||
}
|
}
|
||||||
tokenAddress = config.deployments[`netId${netId}`][currency].tokenAddress
|
tokenAddress = config.deployments[`netId${netId}`][currency].tokenAddress
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.error('There is no such tornado instance, check the currency and amount you provide')
|
console.error('There is no such tornado instance, check the currency and amount you provide')
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
@ -472,6 +579,34 @@ async function main() {
|
||||||
await printERC20Balance({ address, name: '', tokenAddress })
|
await printERC20Balance({ address, name: '', tokenAddress })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
program
|
||||||
|
.command('compliance <note>')
|
||||||
|
.description('Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.')
|
||||||
|
.action(async (noteString) => {
|
||||||
|
const { currency, amount, netId, deposit } = parseNote(noteString)
|
||||||
|
await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
|
||||||
|
const depositInfo = await loadDepositData({ deposit })
|
||||||
|
const depositDate = new Date(depositInfo.timestamp * 1000)
|
||||||
|
console.log('\n=============Deposit=================')
|
||||||
|
console.log('Deposit :', amount, currency)
|
||||||
|
console.log('Date :', depositDate.toLocaleDateString(), depositDate.toLocaleTimeString())
|
||||||
|
console.log('From :', `https://${getCurrentNetworkName()}etherscan.io/address/${depositInfo.from}`)
|
||||||
|
console.log('Transaction :', `https://${getCurrentNetworkName()}etherscan.io/tx/${depositInfo.txHash}`)
|
||||||
|
console.log('Commitment :', depositInfo.commitment)
|
||||||
|
if (deposit.isSpent) {
|
||||||
|
console.log('The note was not spent')
|
||||||
|
}
|
||||||
|
|
||||||
|
const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit })
|
||||||
|
const withdrawalDate = new Date(withdrawInfo.timestamp * 1000)
|
||||||
|
console.log('\n=============Withdrawal==============')
|
||||||
|
console.log('Withdrawal :', withdrawInfo.amount, currency)
|
||||||
|
console.log('Relayer Fee :', withdrawInfo.fee, currency)
|
||||||
|
console.log('Date :', withdrawalDate.toLocaleDateString(), withdrawalDate.toLocaleTimeString())
|
||||||
|
console.log('To :', `https://${getCurrentNetworkName()}etherscan.io/address/${withdrawInfo.to}`)
|
||||||
|
console.log('Transaction :', `https://${getCurrentNetworkName()}etherscan.io/tx/${withdrawInfo.txHash}`)
|
||||||
|
console.log('Nullifier :', withdrawInfo.nullifier)
|
||||||
|
})
|
||||||
program
|
program
|
||||||
.command('test')
|
.command('test')
|
||||||
.description('Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.')
|
.description('Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.')
|
||||||
|
@ -489,13 +624,13 @@ async function main() {
|
||||||
amount = '100'
|
amount = '100'
|
||||||
await init({ rpc: program.rpc, currency, amount })
|
await init({ rpc: program.rpc, currency, amount })
|
||||||
noteString = await deposit({ currency, amount })
|
noteString = await deposit({ currency, amount })
|
||||||
;(parsedNote = parseNote(noteString))
|
; (parsedNote = parseNote(noteString))
|
||||||
await withdraw({ deposit: parsedNote.deposit, currency, amount, recipient: senderAccount, refund: '0.02', relayerURL: program.relayer })
|
await withdraw({ deposit: parsedNote.deposit, currency, amount, recipient: senderAccount, refund: '0.02', relayerURL: program.relayer })
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
await program.parseAsync(process.argv)
|
await program.parseAsync(process.argv)
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.log('Error:', e)
|
console.log('Error:', e)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
deployments: {
|
||||||
|
netId1: {
|
||||||
|
eth: {
|
||||||
|
instanceAddress: {
|
||||||
|
0.1: '0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc',
|
||||||
|
1: '0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936',
|
||||||
|
10: '0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF',
|
||||||
|
100: '0xA160cdAB225685dA1d56aa342Ad8841c3b53f291',
|
||||||
|
},
|
||||||
|
symbol: 'ETH',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
dai: {
|
||||||
|
instanceAddress: {
|
||||||
|
100: '0xD4B88Df4D29F5CedD6857912842cff3b20C8Cfa3',
|
||||||
|
1000: '0xFD8610d20aA15b7B2E3Be39B396a1bC3516c7144',
|
||||||
|
10000: '0xF60dD140cFf0706bAE9Cd734Ac3ae76AD9eBC32A',
|
||||||
|
100000: undefined,
|
||||||
|
},
|
||||||
|
tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
||||||
|
symbol: 'DAI',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
cdai: {
|
||||||
|
instanceAddress: {
|
||||||
|
5000: '0x22aaA7720ddd5388A3c0A3333430953C68f1849b',
|
||||||
|
50000: '0xBA214C1c1928a32Bffe790263E38B4Af9bFCD659',
|
||||||
|
500000: '0xb1C8094B234DcE6e03f10a5b673c1d8C69739A00',
|
||||||
|
5000000: undefined,
|
||||||
|
},
|
||||||
|
tokenAddress: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643',
|
||||||
|
symbol: 'cDAI',
|
||||||
|
decimals: 8,
|
||||||
|
},
|
||||||
|
usdc: {
|
||||||
|
instanceAddress: {
|
||||||
|
100: '0xd96f2B1c14Db8458374d9Aca76E26c3D18364307',
|
||||||
|
1000: '0x4736dCf1b7A3d580672CcE6E7c65cd5cc9cFBa9D',
|
||||||
|
10000: '0xD691F27f38B395864Ea86CfC7253969B409c362d',
|
||||||
|
100000: undefined,
|
||||||
|
},
|
||||||
|
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
||||||
|
symbol: 'USDC',
|
||||||
|
decimals: 6,
|
||||||
|
},
|
||||||
|
cusdc: {
|
||||||
|
instanceAddress: {
|
||||||
|
5000: '0xaEaaC358560e11f52454D997AAFF2c5731B6f8a6',
|
||||||
|
50000: '0x1356c899D8C9467C7f71C195612F8A395aBf2f0a',
|
||||||
|
500000: '0xA60C772958a3eD56c1F15dD055bA37AC8e523a0D',
|
||||||
|
5000000: undefined,
|
||||||
|
},
|
||||||
|
tokenAddress: '0x39AA39c021dfbaE8faC545936693aC917d5E7563',
|
||||||
|
symbol: 'cUSDC',
|
||||||
|
decimals: 8,
|
||||||
|
},
|
||||||
|
usdt: {
|
||||||
|
instanceAddress: {
|
||||||
|
100: '0x169AD27A470D064DEDE56a2D3ff727986b15D52B',
|
||||||
|
1000: '0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f',
|
||||||
|
10000: '0xF67721A2D8F736E75a49FdD7FAd2e31D8676542a',
|
||||||
|
100000: '0x9AD122c22B14202B4490eDAf288FDb3C7cb3ff5E',
|
||||||
|
},
|
||||||
|
tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||||
|
symbol: 'USDT',
|
||||||
|
decimals: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
netId42: {
|
||||||
|
eth: {
|
||||||
|
instanceAddress: {
|
||||||
|
0.1: '0x8b3f5393bA08c24cc7ff5A66a832562aAB7bC95f',
|
||||||
|
1: '0xD6a6AC46d02253c938B96D12BE439F570227aE8E',
|
||||||
|
10: '0xe1BE96331391E519471100c3c1528B66B8F4e5a7',
|
||||||
|
100: '0xd037E0Ac98Dab2fCb7E296c69C6e52767Ae5414D',
|
||||||
|
},
|
||||||
|
symbol: 'ETH',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
dai: {
|
||||||
|
instanceAddress: {
|
||||||
|
100: '0xdf2d3cC5F361CF95b3f62c4bB66deFe3FDE47e3D',
|
||||||
|
1000: '0xD96291dFa35d180a71964D0894a1Ae54247C4ccD',
|
||||||
|
10000: '0xb192794f72EA45e33C3DF6fe212B9c18f6F45AE3',
|
||||||
|
100000: undefined,
|
||||||
|
},
|
||||||
|
tokenAddress: '0x4F96Fe3b7A6Cf9725f59d353F723c1bDb64CA6Aa',
|
||||||
|
symbol: 'DAI',
|
||||||
|
decimals: 18,
|
||||||
|
},
|
||||||
|
cdai: {
|
||||||
|
instanceAddress: {
|
||||||
|
5000: '0x6Fc9386ABAf83147b3a89C36D422c625F44121C8',
|
||||||
|
50000: '0x7182EA067e0f050997444FCb065985Fd677C16b6',
|
||||||
|
500000: '0xC22ceFd90fbd1FdEeE554AE6Cc671179BC3b10Ae',
|
||||||
|
5000000: undefined,
|
||||||
|
},
|
||||||
|
tokenAddress: '0xe7bc397DBd069fC7d0109C0636d06888bb50668c',
|
||||||
|
symbol: 'cDAI',
|
||||||
|
decimals: 8,
|
||||||
|
},
|
||||||
|
usdc: {
|
||||||
|
instanceAddress: {
|
||||||
|
100: '0x137E2B6d185018e7f09f6cf175a970e7fC73826C',
|
||||||
|
1000: '0xcC7f1633A5068E86E3830e692e3e3f8f520525Af',
|
||||||
|
10000: '0x28C8f149a0ab8A9bdB006B8F984fFFCCE52ef5EF',
|
||||||
|
100000: undefined,
|
||||||
|
},
|
||||||
|
tokenAddress: '0x75B0622Cec14130172EaE9Cf166B92E5C112FaFF',
|
||||||
|
symbol: 'USDC',
|
||||||
|
decimals: 6,
|
||||||
|
},
|
||||||
|
cusdc: {
|
||||||
|
instanceAddress: {
|
||||||
|
5000: '0xc0648F28ABA385c8a1421Bbf1B59e3c474F89cB0',
|
||||||
|
50000: '0x0C53853379c6b1A7B74E0A324AcbDD5Eabd4981D',
|
||||||
|
500000: '0xf84016A0E03917cBe700D318EB1b7a53e6e3dEe1',
|
||||||
|
5000000: undefined,
|
||||||
|
},
|
||||||
|
tokenAddress: '0xcfC9bB230F00bFFDB560fCe2428b4E05F3442E35',
|
||||||
|
symbol: 'cUSDC',
|
||||||
|
decimals: 8,
|
||||||
|
},
|
||||||
|
usdt: {
|
||||||
|
instanceAddress: {
|
||||||
|
100: '0x327853Da7916a6A0935563FB1919A48843036b42',
|
||||||
|
1000: '0x531AA4DF5858EA1d0031Dad16e3274609DE5AcC0',
|
||||||
|
10000: '0x0958275F0362cf6f07D21373aEE0cf37dFe415dD',
|
||||||
|
100000: '0x14aEd24B67EaF3FF28503eB92aeb217C47514364',
|
||||||
|
},
|
||||||
|
tokenAddress: '0x03c5F29e9296006876d8DF210BCFfD7EA5Db1Cf1',
|
||||||
|
symbol: 'USDT',
|
||||||
|
decimals: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ const assert = require('assert')
|
||||||
const { bigInt } = require('snarkjs')
|
const { bigInt } = require('snarkjs')
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const circomlib = require('circomlib')
|
const circomlib = require('circomlib')
|
||||||
const merkleTree = require('./lib/MerkleTree')
|
const merkleTree = require('fixed-merkle-tree')
|
||||||
const Web3 = require('web3')
|
const Web3 = require('web3')
|
||||||
const buildGroth16 = require('websnark/src/groth16')
|
const buildGroth16 = require('websnark/src/groth16')
|
||||||
const websnarkUtils = require('websnark/src/utils')
|
const websnarkUtils = require('websnark/src/utils')
|
||||||
|
@ -18,13 +18,15 @@ const AMOUNT = '1'
|
||||||
// CURRENCY = 'ETH'
|
// CURRENCY = 'ETH'
|
||||||
|
|
||||||
/** Generate random number of specified byte length */
|
/** Generate random number of specified byte length */
|
||||||
const rbigint = nbytes => bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
const rbigint = (nbytes) => bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||||
|
|
||||||
/** Compute pedersen hash */
|
/** Compute pedersen hash */
|
||||||
const pedersenHash = data => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
||||||
|
|
||||||
/** BigNumber to hex string of specified length */
|
/** BigNumber to hex string of specified length */
|
||||||
const toHex = (number, length = 32) => '0x' + (number instanceof Buffer ? number.toString('hex') : bigInt(number).toString(16)).padStart(length * 2, '0')
|
const toHex = (number, length = 32) =>
|
||||||
|
'0x' +
|
||||||
|
(number instanceof Buffer ? number.toString('hex') : bigInt(number).toString(16)).padStart(length * 2, '0')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create deposit object from secret and nullifier
|
* Create deposit object from secret and nullifier
|
||||||
|
@ -43,7 +45,9 @@ function createDeposit(nullifier, secret) {
|
||||||
async function deposit() {
|
async function deposit() {
|
||||||
const deposit = createDeposit(rbigint(31), rbigint(31))
|
const deposit = createDeposit(rbigint(31), rbigint(31))
|
||||||
console.log('Sending deposit transaction...')
|
console.log('Sending deposit transaction...')
|
||||||
const tx = await contract.methods.deposit(toHex(deposit.commitment)).send({ value: toWei(AMOUNT), from: web3.eth.defaultAccount, gas:2e6 })
|
const tx = await contract.methods
|
||||||
|
.deposit(toHex(deposit.commitment))
|
||||||
|
.send({ value: toWei(AMOUNT), from: web3.eth.defaultAccount, gas: 2e6 })
|
||||||
console.log(`https://kovan.etherscan.io/tx/${tx.transactionHash}`)
|
console.log(`https://kovan.etherscan.io/tx/${tx.transactionHash}`)
|
||||||
return `tornado-eth-${AMOUNT}-${netId}-${toHex(deposit.preimage, 62)}`
|
return `tornado-eth-${AMOUNT}-${netId}-${toHex(deposit.preimage, 62)}`
|
||||||
}
|
}
|
||||||
|
@ -87,22 +91,23 @@ async function generateMerkleProof(deposit) {
|
||||||
const events = await contract.getPastEvents('Deposit', { fromBlock: 0, toBlock: 'latest' })
|
const events = await contract.getPastEvents('Deposit', { fromBlock: 0, toBlock: 'latest' })
|
||||||
const leaves = events
|
const leaves = events
|
||||||
.sort((a, b) => a.returnValues.leafIndex - b.returnValues.leafIndex) // Sort events in chronological order
|
.sort((a, b) => a.returnValues.leafIndex - b.returnValues.leafIndex) // Sort events in chronological order
|
||||||
.map(e => e.returnValues.commitment)
|
.map((e) => e.returnValues.commitment)
|
||||||
const tree = new merkleTree(MERKLE_TREE_HEIGHT, leaves)
|
const tree = new merkleTree(MERKLE_TREE_HEIGHT, leaves)
|
||||||
|
|
||||||
// Find current commitment in the tree
|
// Find current commitment in the tree
|
||||||
let depositEvent = events.find(e => e.returnValues.commitment === toHex(deposit.commitment))
|
let depositEvent = events.find((e) => e.returnValues.commitment === toHex(deposit.commitment))
|
||||||
let leafIndex = depositEvent ? depositEvent.returnValues.leafIndex : -1
|
let leafIndex = depositEvent ? depositEvent.returnValues.leafIndex : -1
|
||||||
|
|
||||||
// Validate that our data is correct (optional)
|
// Validate that our data is correct (optional)
|
||||||
const isValidRoot = await contract.methods.isKnownRoot(toHex(await tree.root())).call()
|
const isValidRoot = await contract.methods.isKnownRoot(toHex(tree.root())).call()
|
||||||
const isSpent = await contract.methods.isSpent(toHex(deposit.nullifierHash)).call()
|
const isSpent = await contract.methods.isSpent(toHex(deposit.nullifierHash)).call()
|
||||||
assert(isValidRoot === true, 'Merkle tree is corrupted')
|
assert(isValidRoot === true, 'Merkle tree is corrupted')
|
||||||
assert(isSpent === false, 'The note is already spent')
|
assert(isSpent === false, 'The note is already spent')
|
||||||
assert(leafIndex >= 0, 'The deposit is not found in the tree')
|
assert(leafIndex >= 0, 'The deposit is not found in the tree')
|
||||||
|
|
||||||
// Compute merkle proof of our commitment
|
// Compute merkle proof of our commitment
|
||||||
return await tree.path(leafIndex)
|
const { pathElements, pathIndices } = tree.path(leafIndex)
|
||||||
|
return { pathElements, pathIndices, root: tree.root() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,7 +117,7 @@ async function generateMerkleProof(deposit) {
|
||||||
*/
|
*/
|
||||||
async function generateSnarkProof(deposit, recipient) {
|
async function generateSnarkProof(deposit, recipient) {
|
||||||
// Compute merkle proof of our commitment
|
// Compute merkle proof of our commitment
|
||||||
const { root, path_elements, path_index } = await generateMerkleProof(deposit)
|
const { root, pathElements, pathIndices } = await generateMerkleProof(deposit)
|
||||||
|
|
||||||
// Prepare circuit input
|
// Prepare circuit input
|
||||||
const input = {
|
const input = {
|
||||||
|
@ -127,8 +132,8 @@ async function generateSnarkProof(deposit, recipient) {
|
||||||
// Private snark inputs
|
// Private snark inputs
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Generating SNARK proof...')
|
console.log('Generating SNARK proof...')
|
||||||
|
@ -141,19 +146,21 @@ async function generateSnarkProof(deposit, recipient) {
|
||||||
toHex(input.recipient, 20),
|
toHex(input.recipient, 20),
|
||||||
toHex(input.relayer, 20),
|
toHex(input.relayer, 20),
|
||||||
toHex(input.fee),
|
toHex(input.fee),
|
||||||
toHex(input.refund)
|
toHex(input.refund),
|
||||||
]
|
]
|
||||||
|
|
||||||
return { proof, args }
|
return { proof, args }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL, { timeout: 5 * 60 * 1000 }), null, { transactionConfirmationBlocks: 1 })
|
web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL, { timeout: 5 * 60 * 1000 }), null, {
|
||||||
circuit = require('./build/circuits/withdraw.json')
|
transactionConfirmationBlocks: 1,
|
||||||
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
})
|
||||||
|
circuit = require(__dirname + '/../build/circuits/withdraw.json')
|
||||||
|
proving_key = fs.readFileSync(__dirname + '/../build/circuits/withdraw_proving_key.bin').buffer
|
||||||
groth16 = await buildGroth16()
|
groth16 = await buildGroth16()
|
||||||
netId = await web3.eth.net.getId()
|
netId = await web3.eth.net.getId()
|
||||||
contract = new web3.eth.Contract(require('./build/contracts/ETHTornado.json').abi, CONTRACT_ADDRESS)
|
contract = new web3.eth.Contract(require('../build/contracts/ETHTornado.json').abi, CONTRACT_ADDRESS)
|
||||||
const account = web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
|
const account = web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
|
||||||
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
|
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
|
||||||
// eslint-disable-next-line require-atomic-updates
|
// eslint-disable-next-line require-atomic-updates
|
|
@ -1,12 +1,9 @@
|
||||||
/* global artifacts, web3, contract */
|
/* global artifacts, web3, contract */
|
||||||
require('chai')
|
require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
|
||||||
.use(require('bn-chai')(web3.utils.BN))
|
|
||||||
.use(require('chai-as-promised'))
|
|
||||||
.should()
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
const { toBN } = require('web3-utils')
|
const { toBN } = require('web3-utils')
|
||||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper')
|
||||||
|
|
||||||
const Tornado = artifacts.require('./ERC20Tornado.sol')
|
const Tornado = artifacts.require('./ERC20Tornado.sol')
|
||||||
const BadRecipient = artifacts.require('./BadRecipient.sol')
|
const BadRecipient = artifacts.require('./BadRecipient.sol')
|
||||||
|
@ -21,11 +18,15 @@ const snarkjs = require('snarkjs')
|
||||||
const bigInt = snarkjs.bigInt
|
const bigInt = snarkjs.bigInt
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const circomlib = require('circomlib')
|
const circomlib = require('circomlib')
|
||||||
const MerkleTree = require('../lib/MerkleTree')
|
const MerkleTree = require('fixed-merkle-tree')
|
||||||
|
|
||||||
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||||
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
||||||
const toFixedHex = (number, length = 32) => '0x' + bigInt(number).toString(16).padStart(length * 2, '0')
|
const toFixedHex = (number, length = 32) =>
|
||||||
|
'0x' +
|
||||||
|
bigInt(number)
|
||||||
|
.toString(16)
|
||||||
|
.padStart(length * 2, '0')
|
||||||
const getRandomRecipient = () => rbigint(20)
|
const getRandomRecipient = () => rbigint(20)
|
||||||
|
|
||||||
function generateDeposit() {
|
function generateDeposit() {
|
||||||
|
@ -38,7 +39,7 @@ function generateDeposit() {
|
||||||
return deposit
|
return deposit
|
||||||
}
|
}
|
||||||
|
|
||||||
contract('ERC20Tornado', accounts => {
|
contract('ERC20Tornado', (accounts) => {
|
||||||
let tornado
|
let tornado
|
||||||
let token
|
let token
|
||||||
let usdtToken
|
let usdtToken
|
||||||
|
@ -48,7 +49,6 @@ contract('ERC20Tornado', accounts => {
|
||||||
const levels = MERKLE_TREE_HEIGHT || 16
|
const levels = MERKLE_TREE_HEIGHT || 16
|
||||||
let tokenDenomination = TOKEN_AMOUNT || '1000000000000000000' // 1 ether
|
let tokenDenomination = TOKEN_AMOUNT || '1000000000000000000' // 1 ether
|
||||||
let snapshotId
|
let snapshotId
|
||||||
let prefix = 'test'
|
|
||||||
let tree
|
let tree
|
||||||
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
||||||
const refund = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
const refund = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
||||||
|
@ -59,11 +59,7 @@ contract('ERC20Tornado', accounts => {
|
||||||
let proving_key
|
let proving_key
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
tree = new MerkleTree(
|
tree = new MerkleTree(levels)
|
||||||
levels,
|
|
||||||
null,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
tornado = await Tornado.deployed()
|
tornado = await Tornado.deployed()
|
||||||
if (ERC20_TOKEN) {
|
if (ERC20_TOKEN) {
|
||||||
token = await Token.at(ERC20_TOKEN)
|
token = await Token.at(ERC20_TOKEN)
|
||||||
|
@ -111,7 +107,7 @@ contract('ERC20Tornado', accounts => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
const user = accounts[4]
|
const user = accounts[4]
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await token.mint(user, tokenDenomination)
|
await token.mint(user, tokenDenomination)
|
||||||
|
|
||||||
const balanceUserBefore = await token.balanceOf(user)
|
const balanceUserBefore = await token.balanceOf(user)
|
||||||
|
@ -124,11 +120,11 @@ contract('ERC20Tornado', accounts => {
|
||||||
const balanceUserAfter = await token.balanceOf(user)
|
const balanceUserAfter = await token.balanceOf(user)
|
||||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
// Circuit input
|
// Circuit input
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
// public
|
// public
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
relayer,
|
relayer,
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -138,20 +134,19 @@ contract('ERC20Tornado', accounts => {
|
||||||
// private
|
// private
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
|
||||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||||
const balanceRecieverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
||||||
|
|
||||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
|
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||||
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||||
isSpent.should.be.equal(false)
|
isSpent.should.be.equal(false)
|
||||||
|
@ -164,23 +159,25 @@ contract('ERC20Tornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||||
|
|
||||||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
|
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||||
const feeBN = toBN(fee.toString())
|
const feeBN = toBN(fee.toString())
|
||||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
|
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
|
||||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination).sub(feeBN)))
|
balanceReceiverAfter.should.be.eq.BN(
|
||||||
|
toBN(balanceReceiverBefore).add(toBN(tokenDenomination).sub(feeBN)),
|
||||||
|
)
|
||||||
|
|
||||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
|
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
|
||||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(refund)))
|
ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)))
|
||||||
ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore).sub(toBN(refund)))
|
ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore).sub(toBN(refund)))
|
||||||
|
|
||||||
logs[0].event.should.be.equal('Withdrawal')
|
logs[0].event.should.be.equal('Withdrawal')
|
||||||
|
@ -195,7 +192,7 @@ contract('ERC20Tornado', accounts => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
const user = accounts[4]
|
const user = accounts[4]
|
||||||
recipient = bigInt(badRecipient.address)
|
recipient = bigInt(badRecipient.address)
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await token.mint(user, tokenDenomination)
|
await token.mint(user, tokenDenomination)
|
||||||
|
|
||||||
const balanceUserBefore = await token.balanceOf(user)
|
const balanceUserBefore = await token.balanceOf(user)
|
||||||
|
@ -205,11 +202,11 @@ contract('ERC20Tornado', accounts => {
|
||||||
const balanceUserAfter = await token.balanceOf(user)
|
const balanceUserAfter = await token.balanceOf(user)
|
||||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
// Circuit input
|
// Circuit input
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
// public
|
// public
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
relayer,
|
relayer,
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -219,20 +216,19 @@ contract('ERC20Tornado', accounts => {
|
||||||
// private
|
// private
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
|
||||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||||
const balanceRecieverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
||||||
|
|
||||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
|
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||||
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||||
isSpent.should.be.equal(false)
|
isSpent.should.be.equal(false)
|
||||||
|
@ -243,23 +239,25 @@ contract('ERC20Tornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||||
|
|
||||||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
|
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||||
const feeBN = toBN(fee.toString())
|
const feeBN = toBN(fee.toString())
|
||||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
|
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
|
||||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination).sub(feeBN)))
|
balanceReceiverAfter.should.be.eq.BN(
|
||||||
|
toBN(balanceReceiverBefore).add(toBN(tokenDenomination).sub(feeBN)),
|
||||||
|
)
|
||||||
|
|
||||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
|
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
|
||||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore))
|
ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore))
|
||||||
ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore))
|
ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore))
|
||||||
|
|
||||||
logs[0].event.should.be.equal('Withdrawal')
|
logs[0].event.should.be.equal('Withdrawal')
|
||||||
|
@ -273,17 +271,16 @@ contract('ERC20Tornado', accounts => {
|
||||||
it('should reject with wrong refund value', async () => {
|
it('should reject with wrong refund value', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
const user = accounts[4]
|
const user = accounts[4]
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await token.mint(user, tokenDenomination)
|
await token.mint(user, tokenDenomination)
|
||||||
await token.approve(tornado.address, tokenDenomination, { from: user })
|
await token.approve(tornado.address, tokenDenomination, { from: user })
|
||||||
await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||||
|
|
||||||
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
|
||||||
// Circuit input
|
// Circuit input
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
// public
|
// public
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
relayer,
|
relayer,
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -293,11 +290,10 @@ contract('ERC20Tornado', accounts => {
|
||||||
// private
|
// private
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
|
||||||
|
@ -307,13 +303,16 @@ contract('ERC20Tornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
let { reason } = await tornado.withdraw(proof, ...args, { value: 1, from: relayer, gasPrice: '0' }).should.be.rejected
|
let { reason } = await tornado.withdraw(proof, ...args, { value: 1, from: relayer, gasPrice: '0' })
|
||||||
|
.should.be.rejected
|
||||||
reason.should.be.equal('Incorrect refund amount received by the contract')
|
reason.should.be.equal('Incorrect refund amount received by the contract')
|
||||||
|
;({ reason } = await tornado.withdraw(proof, ...args, {
|
||||||
|
value: toBN(refund).mul(toBN(2)),
|
||||||
;({ reason } = await tornado.withdraw(proof, ...args, { value: toBN(refund).mul(toBN(2)), from: relayer, gasPrice: '0' }).should.be.rejected)
|
from: relayer,
|
||||||
|
gasPrice: '0',
|
||||||
|
}).should.be.rejected)
|
||||||
reason.should.be.equal('Incorrect refund amount received by the contract')
|
reason.should.be.equal('Incorrect refund amount received by the contract')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -329,7 +328,7 @@ contract('ERC20Tornado', accounts => {
|
||||||
console.log('userBal', userBal.toString())
|
console.log('userBal', userBal.toString())
|
||||||
const senderBal = await usdtToken.balanceOf(sender)
|
const senderBal = await usdtToken.balanceOf(sender)
|
||||||
console.log('senderBal', senderBal.toString())
|
console.log('senderBal', senderBal.toString())
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await usdtToken.transfer(user, tokenDenomination, { from: sender })
|
await usdtToken.transfer(user, tokenDenomination, { from: sender })
|
||||||
console.log('transfer done')
|
console.log('transfer done')
|
||||||
|
|
||||||
|
@ -345,12 +344,12 @@ contract('ERC20Tornado', accounts => {
|
||||||
const balanceUserAfter = await usdtToken.balanceOf(user)
|
const balanceUserAfter = await usdtToken.balanceOf(user)
|
||||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
// Circuit input
|
// Circuit input
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
// public
|
// public
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -360,19 +359,18 @@ contract('ERC20Tornado', accounts => {
|
||||||
// private
|
// private
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
|
||||||
const balanceTornadoBefore = await usdtToken.balanceOf(tornado.address)
|
const balanceTornadoBefore = await usdtToken.balanceOf(tornado.address)
|
||||||
const balanceRelayerBefore = await usdtToken.balanceOf(relayer)
|
const balanceRelayerBefore = await usdtToken.balanceOf(relayer)
|
||||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||||
const balanceRecieverBefore = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
const balanceReceiverBefore = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||||
isSpent.should.be.equal(false)
|
isSpent.should.be.equal(false)
|
||||||
|
|
||||||
|
@ -385,22 +383,21 @@ contract('ERC20Tornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||||
|
|
||||||
const balanceTornadoAfter = await usdtToken.balanceOf(tornado.address)
|
const balanceTornadoAfter = await usdtToken.balanceOf(tornado.address)
|
||||||
const balanceRelayerAfter = await usdtToken.balanceOf(relayer)
|
const balanceRelayerAfter = await usdtToken.balanceOf(relayer)
|
||||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||||
const balanceRecieverAfter = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
const balanceReceiverAfter = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
const feeBN = toBN(fee.toString())
|
const feeBN = toBN(fee.toString())
|
||||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
|
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
|
||||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination)))
|
balanceReceiverAfter.should.be.eq.BN(toBN(balanceReceiverBefore).add(toBN(tokenDenomination)))
|
||||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(refund)).sub(feeBN))
|
ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)).sub(feeBN))
|
||||||
|
|
||||||
|
|
||||||
logs[0].event.should.be.equal('Withdrawal')
|
logs[0].event.should.be.equal('Withdrawal')
|
||||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||||
|
@ -420,7 +417,7 @@ contract('ERC20Tornado', accounts => {
|
||||||
console.log('userBal', userBal.toString())
|
console.log('userBal', userBal.toString())
|
||||||
const senderBal = await token.balanceOf(sender)
|
const senderBal = await token.balanceOf(sender)
|
||||||
console.log('senderBal', senderBal.toString())
|
console.log('senderBal', senderBal.toString())
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await token.transfer(user, tokenDenomination, { from: sender })
|
await token.transfer(user, tokenDenomination, { from: sender })
|
||||||
console.log('transfer done')
|
console.log('transfer done')
|
||||||
|
|
||||||
|
@ -434,12 +431,12 @@ contract('ERC20Tornado', accounts => {
|
||||||
const balanceUserAfter = await token.balanceOf(user)
|
const balanceUserAfter = await token.balanceOf(user)
|
||||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
// Circuit input
|
// Circuit input
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
// public
|
// public
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -449,19 +446,18 @@ contract('ERC20Tornado', accounts => {
|
||||||
// private
|
// private
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
|
||||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||||
const balanceRecieverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||||
isSpent.should.be.equal(false)
|
isSpent.should.be.equal(false)
|
||||||
|
|
||||||
|
@ -474,7 +470,7 @@ contract('ERC20Tornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||||
console.log('withdraw done')
|
console.log('withdraw done')
|
||||||
|
@ -482,15 +478,14 @@ contract('ERC20Tornado', accounts => {
|
||||||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
const feeBN = toBN(fee.toString())
|
const feeBN = toBN(fee.toString())
|
||||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
|
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
|
||||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination)))
|
balanceReceiverAfter.should.be.eq.BN(toBN(balanceReceiverBefore).add(toBN(tokenDenomination)))
|
||||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(refund)).sub(feeBN))
|
ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)).sub(feeBN))
|
||||||
|
|
||||||
|
|
||||||
logs[0].event.should.be.equal('Withdrawal')
|
logs[0].event.should.be.equal('Withdrawal')
|
||||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||||
|
@ -505,10 +500,6 @@ contract('ERC20Tornado', accounts => {
|
||||||
await revertSnapshot(snapshotId.result)
|
await revertSnapshot(snapshotId.result)
|
||||||
// eslint-disable-next-line require-atomic-updates
|
// eslint-disable-next-line require-atomic-updates
|
||||||
snapshotId = await takeSnapshot()
|
snapshotId = await takeSnapshot()
|
||||||
tree = new MerkleTree(
|
tree = new MerkleTree(levels)
|
||||||
levels,
|
|
||||||
null,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
/* global artifacts, web3, contract */
|
/* global artifacts, web3, contract */
|
||||||
require('chai')
|
require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
|
||||||
.use(require('bn-chai')(web3.utils.BN))
|
|
||||||
.use(require('chai-as-promised'))
|
|
||||||
.should()
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
const { toBN, randomHex } = require('web3-utils')
|
const { toBN, randomHex } = require('web3-utils')
|
||||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper')
|
||||||
|
|
||||||
const Tornado = artifacts.require('./ETHTornado.sol')
|
const Tornado = artifacts.require('./ETHTornado.sol')
|
||||||
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT } = process.env
|
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT } = process.env
|
||||||
|
@ -19,11 +16,15 @@ const snarkjs = require('snarkjs')
|
||||||
const bigInt = snarkjs.bigInt
|
const bigInt = snarkjs.bigInt
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const circomlib = require('circomlib')
|
const circomlib = require('circomlib')
|
||||||
const MerkleTree = require('../lib/MerkleTree')
|
const MerkleTree = require('fixed-merkle-tree')
|
||||||
|
|
||||||
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||||
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
||||||
const toFixedHex = (number, length = 32) => '0x' + bigInt(number).toString(16).padStart(length * 2, '0')
|
const toFixedHex = (number, length = 32) =>
|
||||||
|
'0x' +
|
||||||
|
bigInt(number)
|
||||||
|
.toString(16)
|
||||||
|
.padStart(length * 2, '0')
|
||||||
const getRandomRecipient = () => rbigint(20)
|
const getRandomRecipient = () => rbigint(20)
|
||||||
|
|
||||||
function generateDeposit() {
|
function generateDeposit() {
|
||||||
|
@ -39,7 +40,7 @@ function generateDeposit() {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
function BNArrayToStringArray(array) {
|
function BNArrayToStringArray(array) {
|
||||||
const arrayToPrint = []
|
const arrayToPrint = []
|
||||||
array.forEach(item => {
|
array.forEach((item) => {
|
||||||
arrayToPrint.push(item.toString())
|
arrayToPrint.push(item.toString())
|
||||||
})
|
})
|
||||||
return arrayToPrint
|
return arrayToPrint
|
||||||
|
@ -51,14 +52,13 @@ function snarkVerify(proof) {
|
||||||
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
|
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
|
||||||
}
|
}
|
||||||
|
|
||||||
contract('ETHTornado', accounts => {
|
contract('ETHTornado', (accounts) => {
|
||||||
let tornado
|
let tornado
|
||||||
const sender = accounts[0]
|
const sender = accounts[0]
|
||||||
const operator = accounts[0]
|
const operator = accounts[0]
|
||||||
const levels = MERKLE_TREE_HEIGHT || 16
|
const levels = MERKLE_TREE_HEIGHT || 16
|
||||||
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
||||||
let snapshotId
|
let snapshotId
|
||||||
let prefix = 'test'
|
|
||||||
let tree
|
let tree
|
||||||
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
||||||
const refund = bigInt(0)
|
const refund = bigInt(0)
|
||||||
|
@ -69,11 +69,7 @@ contract('ETHTornado', accounts => {
|
||||||
let proving_key
|
let proving_key
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
tree = new MerkleTree(
|
tree = new MerkleTree(levels)
|
||||||
levels,
|
|
||||||
null,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
tornado = await Tornado.deployed()
|
tornado = await Tornado.deployed()
|
||||||
snapshotId = await takeSnapshot()
|
snapshotId = await takeSnapshot()
|
||||||
groth16 = await buildGroth16()
|
groth16 = await buildGroth16()
|
||||||
|
@ -97,8 +93,8 @@ contract('ETHTornado', accounts => {
|
||||||
logs[0].args.commitment.should.be.equal(commitment)
|
logs[0].args.commitment.should.be.equal(commitment)
|
||||||
logs[0].args.leafIndex.should.be.eq.BN(0)
|
logs[0].args.leafIndex.should.be.eq.BN(0)
|
||||||
|
|
||||||
commitment = toFixedHex(12);
|
commitment = toFixedHex(12)
|
||||||
({ logs } = await tornado.deposit(commitment, { value, from: accounts[2] }))
|
;({ logs } = await tornado.deposit(commitment, { value, from: accounts[2] }))
|
||||||
|
|
||||||
logs[0].event.should.be.equal('Deposit')
|
logs[0].event.should.be.equal('Deposit')
|
||||||
logs[0].args.commitment.should.be.equal(commitment)
|
logs[0].args.commitment.should.be.equal(commitment)
|
||||||
|
@ -116,11 +112,11 @@ contract('ETHTornado', accounts => {
|
||||||
describe('snark proof verification on js side', () => {
|
describe('snark proof verification on js side', () => {
|
||||||
it('should detect tampering', async () => {
|
it('should detect tampering', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
|
@ -128,8 +124,8 @@ contract('ETHTornado', accounts => {
|
||||||
fee,
|
fee,
|
||||||
refund,
|
refund,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
let proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
let proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
|
@ -138,7 +134,8 @@ contract('ETHTornado', accounts => {
|
||||||
result.should.be.equal(true)
|
result.should.be.equal(true)
|
||||||
|
|
||||||
// nullifier
|
// nullifier
|
||||||
proofData.publicSignals[1] = '133792158246920651341275668520530514036799294649489851421007411546007850802'
|
proofData.publicSignals[1] =
|
||||||
|
'133792158246920651341275668520530514036799294649489851421007411546007850802'
|
||||||
result = snarkVerify(proofData)
|
result = snarkVerify(proofData)
|
||||||
result.should.be.equal(false)
|
result.should.be.equal(false)
|
||||||
proofData = originalProof
|
proofData = originalProof
|
||||||
|
@ -161,7 +158,7 @@ contract('ETHTornado', accounts => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
const user = accounts[4]
|
const user = accounts[4]
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
|
|
||||||
const balanceUserBefore = await web3.eth.getBalance(user)
|
const balanceUserBefore = await web3.eth.getBalance(user)
|
||||||
|
|
||||||
|
@ -173,12 +170,12 @@ contract('ETHTornado', accounts => {
|
||||||
const balanceUserAfter = await web3.eth.getBalance(user)
|
const balanceUserAfter = await web3.eth.getBalance(user)
|
||||||
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(value)))
|
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(value)))
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
// Circuit input
|
// Circuit input
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
// public
|
// public
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -188,18 +185,17 @@ contract('ETHTornado', accounts => {
|
||||||
// private
|
// private
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
|
||||||
const balanceTornadoBefore = await web3.eth.getBalance(tornado.address)
|
const balanceTornadoBefore = await web3.eth.getBalance(tornado.address)
|
||||||
const balanceRelayerBefore = await web3.eth.getBalance(relayer)
|
const balanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||||
const balanceOperatorBefore = await web3.eth.getBalance(operator)
|
const balanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||||
const balanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const balanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||||
isSpent.should.be.equal(false)
|
isSpent.should.be.equal(false)
|
||||||
|
|
||||||
|
@ -212,20 +208,19 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const { logs } = await tornado.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
const { logs } = await tornado.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
||||||
|
|
||||||
const balanceTornadoAfter = await web3.eth.getBalance(tornado.address)
|
const balanceTornadoAfter = await web3.eth.getBalance(tornado.address)
|
||||||
const balanceRelayerAfter = await web3.eth.getBalance(relayer)
|
const balanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||||
const balanceOperatorAfter = await web3.eth.getBalance(operator)
|
const balanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||||
const balanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
const balanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||||
const feeBN = toBN(fee.toString())
|
const feeBN = toBN(fee.toString())
|
||||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(value)))
|
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(value)))
|
||||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||||
balanceOperatorAfter.should.be.eq.BN(toBN(balanceOperatorBefore).add(feeBN))
|
balanceOperatorAfter.should.be.eq.BN(toBN(balanceOperatorBefore).add(feeBN))
|
||||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(value)).sub(feeBN))
|
balanceReceiverAfter.should.be.eq.BN(toBN(balanceReceiverBefore).add(toBN(value)).sub(feeBN))
|
||||||
|
|
||||||
|
|
||||||
logs[0].event.should.be.equal('Withdrawal')
|
logs[0].event.should.be.equal('Withdrawal')
|
||||||
logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
|
logs[0].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
|
||||||
|
@ -237,13 +232,13 @@ contract('ETHTornado', accounts => {
|
||||||
|
|
||||||
it('should prevent double spend', async () => {
|
it('should prevent double spend', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
|
@ -251,8 +246,8 @@ contract('ETHTornado', accounts => {
|
||||||
fee,
|
fee,
|
||||||
refund,
|
refund,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
@ -262,7 +257,7 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
await tornado.withdraw(proof, ...args, { from: relayer }).should.be.fulfilled
|
await tornado.withdraw(proof, ...args, { from: relayer }).should.be.fulfilled
|
||||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||||
|
@ -271,13 +266,13 @@ contract('ETHTornado', accounts => {
|
||||||
|
|
||||||
it('should prevent double spend with overflow', async () => {
|
it('should prevent double spend with overflow', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
|
@ -285,18 +280,22 @@ contract('ETHTornado', accounts => {
|
||||||
fee,
|
fee,
|
||||||
refund,
|
refund,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
const args = [
|
const args = [
|
||||||
toFixedHex(input.root),
|
toFixedHex(input.root),
|
||||||
toFixedHex(toBN(input.nullifierHash).add(toBN('21888242871839275222246405745257275088548364400416034343698204186575808495617'))),
|
toFixedHex(
|
||||||
|
toBN(input.nullifierHash).add(
|
||||||
|
toBN('21888242871839275222246405745257275088548364400416034343698204186575808495617'),
|
||||||
|
),
|
||||||
|
),
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||||
error.reason.should.be.equal('verifier-gte-snark-scalar-field')
|
error.reason.should.be.equal('verifier-gte-snark-scalar-field')
|
||||||
|
@ -304,13 +303,13 @@ contract('ETHTornado', accounts => {
|
||||||
|
|
||||||
it('fee should be less or equal transfer value', async () => {
|
it('fee should be less or equal transfer value', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
const largeFee = bigInt(value).add(bigInt(1))
|
const largeFee = bigInt(value).add(bigInt(1))
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
|
@ -318,8 +317,8 @@ contract('ETHTornado', accounts => {
|
||||||
fee: largeFee,
|
fee: largeFee,
|
||||||
refund,
|
refund,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
|
@ -330,7 +329,7 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||||
error.reason.should.be.equal('Fee exceeds transfer value')
|
error.reason.should.be.equal('Fee exceeds transfer value')
|
||||||
|
@ -338,22 +337,22 @@ contract('ETHTornado', accounts => {
|
||||||
|
|
||||||
it('should throw for corrupted merkle tree root', async () => {
|
it('should throw for corrupted merkle tree root', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
recipient,
|
recipient,
|
||||||
fee,
|
fee,
|
||||||
refund,
|
refund,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
|
@ -365,7 +364,7 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||||
error.reason.should.be.equal('Cannot find your merkle root')
|
error.reason.should.be.equal('Cannot find your merkle root')
|
||||||
|
@ -373,13 +372,13 @@ contract('ETHTornado', accounts => {
|
||||||
|
|
||||||
it('should reject with tampered public inputs', async () => {
|
it('should reject with tampered public inputs', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||||
|
|
||||||
let { root, path_elements, path_index } = await tree.path(0)
|
let { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
|
@ -387,8 +386,8 @@ contract('ETHTornado', accounts => {
|
||||||
fee,
|
fee,
|
||||||
refund,
|
refund,
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
let { proof } = websnarkUtils.toSolidityInput(proofData)
|
let { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
@ -398,7 +397,7 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
let incorrectArgs
|
let incorrectArgs
|
||||||
const originalProof = proof.slice()
|
const originalProof = proof.slice()
|
||||||
|
@ -410,7 +409,7 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex('0x0000000000000000000000007a1f9131357404ef86d7c38dbffed2da70321337', 20),
|
toFixedHex('0x0000000000000000000000007a1f9131357404ef86d7c38dbffed2da70321337', 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
let error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
let error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||||
error.reason.should.be.equal('Invalid withdraw proof')
|
error.reason.should.be.equal('Invalid withdraw proof')
|
||||||
|
@ -422,7 +421,7 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex('0x000000000000000000000000000000000000000000000000015345785d8a0000'),
|
toFixedHex('0x000000000000000000000000000000000000000000000000015345785d8a0000'),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||||
error.reason.should.be.equal('Invalid withdraw proof')
|
error.reason.should.be.equal('Invalid withdraw proof')
|
||||||
|
@ -434,7 +433,7 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||||
error.reason.should.be.equal('Invalid withdraw proof')
|
error.reason.should.be.equal('Invalid withdraw proof')
|
||||||
|
@ -449,22 +448,22 @@ contract('ETHTornado', accounts => {
|
||||||
|
|
||||||
it('should reject with non zero refund', async () => {
|
it('should reject with non zero refund', async () => {
|
||||||
const deposit = generateDeposit()
|
const deposit = generateDeposit()
|
||||||
await tree.insert(deposit.commitment)
|
tree.insert(deposit.commitment)
|
||||||
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
await tornado.deposit(toFixedHex(deposit.commitment), { value, from: sender })
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(0)
|
const { pathElements, pathIndices } = tree.path(0)
|
||||||
|
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifier: deposit.nullifier,
|
nullifier: deposit.nullifier,
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
recipient,
|
recipient,
|
||||||
fee,
|
fee,
|
||||||
refund: bigInt(1),
|
refund: bigInt(1),
|
||||||
secret: deposit.secret,
|
secret: deposit.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
|
@ -476,74 +475,28 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||||
error.reason.should.be.equal('Refund value is supposed to be zero for ETH instance')
|
error.reason.should.be.equal('Refund value is supposed to be zero for ETH instance')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#changeOperator', () => {
|
|
||||||
it('should work', async () => {
|
|
||||||
let operator = await tornado.operator()
|
|
||||||
operator.should.be.equal(sender)
|
|
||||||
|
|
||||||
const newOperator = accounts[7]
|
|
||||||
await tornado.changeOperator(newOperator).should.be.fulfilled
|
|
||||||
|
|
||||||
operator = await tornado.operator()
|
|
||||||
operator.should.be.equal(newOperator)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cannot change from different address', async () => {
|
|
||||||
let operator = await tornado.operator()
|
|
||||||
operator.should.be.equal(sender)
|
|
||||||
|
|
||||||
const newOperator = accounts[7]
|
|
||||||
const error = await tornado.changeOperator(newOperator, { from: accounts[7] }).should.be.rejected
|
|
||||||
error.reason.should.be.equal('Only operator can call this function.')
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#updateVerifier', () => {
|
|
||||||
it('should work', async () => {
|
|
||||||
let operator = await tornado.operator()
|
|
||||||
operator.should.be.equal(sender)
|
|
||||||
|
|
||||||
const newVerifier = accounts[7]
|
|
||||||
await tornado.updateVerifier(newVerifier).should.be.fulfilled
|
|
||||||
|
|
||||||
const verifier = await tornado.verifier()
|
|
||||||
verifier.should.be.equal(newVerifier)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('cannot change from different address', async () => {
|
|
||||||
let operator = await tornado.operator()
|
|
||||||
operator.should.be.equal(sender)
|
|
||||||
|
|
||||||
const newVerifier = accounts[7]
|
|
||||||
const error = await tornado.updateVerifier(newVerifier, { from: accounts[7] }).should.be.rejected
|
|
||||||
error.reason.should.be.equal('Only operator can call this function.')
|
|
||||||
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#isSpent', () => {
|
describe('#isSpent', () => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const deposit1 = generateDeposit()
|
const deposit1 = generateDeposit()
|
||||||
const deposit2 = generateDeposit()
|
const deposit2 = generateDeposit()
|
||||||
await tree.insert(deposit1.commitment)
|
tree.insert(deposit1.commitment)
|
||||||
await tree.insert(deposit2.commitment)
|
tree.insert(deposit2.commitment)
|
||||||
await tornado.deposit(toFixedHex(deposit1.commitment), { value, gasPrice: '0' })
|
await tornado.deposit(toFixedHex(deposit1.commitment), { value, gasPrice: '0' })
|
||||||
await tornado.deposit(toFixedHex(deposit2.commitment), { value, gasPrice: '0' })
|
await tornado.deposit(toFixedHex(deposit2.commitment), { value, gasPrice: '0' })
|
||||||
|
|
||||||
const { root, path_elements, path_index } = await tree.path(1)
|
const { pathElements, pathIndices } = tree.path(1)
|
||||||
|
|
||||||
// Circuit input
|
// Circuit input
|
||||||
const input = stringifyBigInts({
|
const input = stringifyBigInts({
|
||||||
// public
|
// public
|
||||||
root,
|
root: tree.root(),
|
||||||
nullifierHash: pedersenHash(deposit2.nullifier.leInt2Buff(31)),
|
nullifierHash: pedersenHash(deposit2.nullifier.leInt2Buff(31)),
|
||||||
relayer: operator,
|
relayer: operator,
|
||||||
recipient,
|
recipient,
|
||||||
|
@ -553,11 +506,10 @@ contract('ETHTornado', accounts => {
|
||||||
// private
|
// private
|
||||||
nullifier: deposit2.nullifier,
|
nullifier: deposit2.nullifier,
|
||||||
secret: deposit2.secret,
|
secret: deposit2.secret,
|
||||||
pathElements: path_elements,
|
pathElements: pathElements,
|
||||||
pathIndices: path_index,
|
pathIndices: pathIndices,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||||
|
|
||||||
|
@ -567,7 +519,7 @@ contract('ETHTornado', accounts => {
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(input.recipient, 20),
|
||||||
toFixedHex(input.relayer, 20),
|
toFixedHex(input.relayer, 20),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.refund)
|
toFixedHex(input.refund),
|
||||||
]
|
]
|
||||||
|
|
||||||
await tornado.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
await tornado.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
||||||
|
@ -583,10 +535,6 @@ contract('ETHTornado', accounts => {
|
||||||
await revertSnapshot(snapshotId.result)
|
await revertSnapshot(snapshotId.result)
|
||||||
// eslint-disable-next-line require-atomic-updates
|
// eslint-disable-next-line require-atomic-updates
|
||||||
snapshotId = await takeSnapshot()
|
snapshotId = await takeSnapshot()
|
||||||
tree = new MerkleTree(
|
tree = new MerkleTree(levels)
|
||||||
levels,
|
|
||||||
null,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
/* global artifacts, web3, contract, assert */
|
/* global artifacts, web3, contract */
|
||||||
require('chai')
|
require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
|
||||||
.use(require('bn-chai')(web3.utils.BN))
|
|
||||||
.use(require('chai-as-promised'))
|
|
||||||
.should()
|
|
||||||
|
|
||||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper')
|
||||||
|
|
||||||
const MerkleTreeWithHistory = artifacts.require('./MerkleTreeWithHistoryMock.sol')
|
const MerkleTreeWithHistory = artifacts.require('./MerkleTreeWithHistoryMock.sol')
|
||||||
const hasherContract = artifacts.require('./Hasher.sol')
|
const hasherContract = artifacts.require('./Hasher.sol')
|
||||||
|
|
||||||
const MerkleTree = require('../lib/MerkleTree')
|
const MerkleTree = require('fixed-merkle-tree')
|
||||||
const hasherImpl = require('../lib/MiMC')
|
|
||||||
|
|
||||||
const snarkjs = require('snarkjs')
|
const snarkjs = require('snarkjs')
|
||||||
const bigInt = snarkjs.bigInt
|
const bigInt = snarkjs.bigInt
|
||||||
|
@ -20,7 +16,7 @@ const { ETH_AMOUNT, MERKLE_TREE_HEIGHT } = process.env
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
function BNArrayToStringArray(array) {
|
function BNArrayToStringArray(array) {
|
||||||
const arrayToPrint = []
|
const arrayToPrint = []
|
||||||
array.forEach(item => {
|
array.forEach((item) => {
|
||||||
arrayToPrint.push(item.toString())
|
arrayToPrint.push(item.toString())
|
||||||
})
|
})
|
||||||
return arrayToPrint
|
return arrayToPrint
|
||||||
|
@ -33,7 +29,7 @@ function toFixedHex(number, length = 32) {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
contract('MerkleTreeWithHistory', accounts => {
|
contract('MerkleTreeWithHistory', (accounts) => {
|
||||||
let merkleTreeWithHistory
|
let merkleTreeWithHistory
|
||||||
let hasherInstance
|
let hasherInstance
|
||||||
let levels = MERKLE_TREE_HEIGHT || 16
|
let levels = MERKLE_TREE_HEIGHT || 16
|
||||||
|
@ -41,19 +37,12 @@ contract('MerkleTreeWithHistory', accounts => {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const value = ETH_AMOUNT || '1000000000000000000'
|
const value = ETH_AMOUNT || '1000000000000000000'
|
||||||
let snapshotId
|
let snapshotId
|
||||||
let prefix = 'test'
|
|
||||||
let tree
|
let tree
|
||||||
let hasher
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
tree = new MerkleTree(
|
tree = new MerkleTree(levels)
|
||||||
levels,
|
|
||||||
null,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
hasherInstance = await hasherContract.deployed()
|
hasherInstance = await hasherContract.deployed()
|
||||||
await MerkleTreeWithHistory.link(hasherContract, hasherInstance.address)
|
merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, hasherInstance.address)
|
||||||
merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels)
|
|
||||||
snapshotId = await takeSnapshot()
|
snapshotId = await takeSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -67,132 +56,31 @@ contract('MerkleTreeWithHistory', accounts => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('merkleTreeLib', () => {
|
|
||||||
it('index_to_key', () => {
|
|
||||||
assert.equal(
|
|
||||||
MerkleTree.index_to_key('test', 5, 20),
|
|
||||||
'test_tree_5_20',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('tests insert', async () => {
|
|
||||||
hasher = new hasherImpl()
|
|
||||||
tree = new MerkleTree(
|
|
||||||
2,
|
|
||||||
null,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
await tree.insert(toFixedHex('5'))
|
|
||||||
let { root, path_elements } = await tree.path(0)
|
|
||||||
const calculated_root = hasher.hash(null,
|
|
||||||
hasher.hash(null, '5', path_elements[0]),
|
|
||||||
path_elements[1]
|
|
||||||
)
|
|
||||||
// console.log(root)
|
|
||||||
assert.equal(root, calculated_root)
|
|
||||||
})
|
|
||||||
it('creation odd elements count', async () => {
|
|
||||||
const elements = [12, 13, 14, 15, 16, 17, 18, 19, 20]
|
|
||||||
for(const [, el] of Object.entries(elements)) {
|
|
||||||
await tree.insert(el)
|
|
||||||
}
|
|
||||||
|
|
||||||
const batchTree = new MerkleTree(
|
|
||||||
levels,
|
|
||||||
elements,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
for(const [i] of Object.entries(elements)) {
|
|
||||||
const pathViaConstructor = await batchTree.path(i)
|
|
||||||
const pathViaUpdate = await tree.path(i)
|
|
||||||
pathViaConstructor.should.be.deep.equal(pathViaUpdate)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should find an element', async () => {
|
|
||||||
const elements = [12, 13, 14, 15, 16, 17, 18, 19, 20]
|
|
||||||
for(const [, el] of Object.entries(elements)) {
|
|
||||||
await tree.insert(el)
|
|
||||||
}
|
|
||||||
let index = tree.getIndexByElement(13)
|
|
||||||
index.should.be.equal(1)
|
|
||||||
|
|
||||||
index = tree.getIndexByElement(19)
|
|
||||||
index.should.be.equal(7)
|
|
||||||
|
|
||||||
index = tree.getIndexByElement(12)
|
|
||||||
index.should.be.equal(0)
|
|
||||||
|
|
||||||
index = tree.getIndexByElement(20)
|
|
||||||
index.should.be.equal(8)
|
|
||||||
|
|
||||||
index = tree.getIndexByElement(42)
|
|
||||||
index.should.be.equal(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('creation even elements count', async () => {
|
|
||||||
const elements = [12, 13, 14, 15, 16, 17]
|
|
||||||
for(const [, el] of Object.entries(elements)) {
|
|
||||||
await tree.insert(el)
|
|
||||||
}
|
|
||||||
|
|
||||||
const batchTree = new MerkleTree(
|
|
||||||
levels,
|
|
||||||
elements,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
for(const [i] of Object.entries(elements)) {
|
|
||||||
const pathViaConstructor = await batchTree.path(i)
|
|
||||||
const pathViaUpdate = await tree.path(i)
|
|
||||||
pathViaConstructor.should.be.deep.equal(pathViaUpdate)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it.skip('creation using 30000 elements', () => {
|
|
||||||
const elements = []
|
|
||||||
for(let i = 1000; i < 31001; i++) {
|
|
||||||
elements.push(i)
|
|
||||||
}
|
|
||||||
console.time('MerkleTree')
|
|
||||||
tree = new MerkleTree(
|
|
||||||
levels,
|
|
||||||
elements,
|
|
||||||
prefix,
|
|
||||||
)
|
|
||||||
console.timeEnd('MerkleTree')
|
|
||||||
// 2,7 GHz Intel Core i7
|
|
||||||
// 1000 : 1949.084ms
|
|
||||||
// 10000: 19456.220ms
|
|
||||||
// 30000: 63406.679ms
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#insert', () => {
|
describe('#insert', () => {
|
||||||
it('should insert', async () => {
|
it('should insert', async () => {
|
||||||
let rootFromContract
|
let rootFromContract
|
||||||
|
|
||||||
for (let i = 1; i < 11; i++) {
|
for (let i = 1; i < 11; i++) {
|
||||||
await merkleTreeWithHistory.insert(toFixedHex(i), { from: sender })
|
await merkleTreeWithHistory.insert(toFixedHex(i), { from: sender })
|
||||||
await tree.insert(i)
|
tree.insert(i)
|
||||||
let { root } = await tree.path(i - 1)
|
|
||||||
rootFromContract = await merkleTreeWithHistory.getLastRoot()
|
rootFromContract = await merkleTreeWithHistory.getLastRoot()
|
||||||
toFixedHex(root).should.be.equal(rootFromContract.toString())
|
toFixedHex(tree.root()).should.be.equal(rootFromContract.toString())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should reject if tree is full', async () => {
|
it('should reject if tree is full', async () => {
|
||||||
const levels = 6
|
const levels = 6
|
||||||
const merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels)
|
const merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, hasherInstance.address)
|
||||||
|
|
||||||
for (let i = 0; i < 2**levels; i++) {
|
for (let i = 0; i < 2 ** levels; i++) {
|
||||||
await merkleTreeWithHistory.insert(toFixedHex(i+42)).should.be.fulfilled
|
await merkleTreeWithHistory.insert(toFixedHex(i + 42)).should.be.fulfilled
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = await merkleTreeWithHistory.insert(toFixedHex(1337)).should.be.rejected
|
let error = await merkleTreeWithHistory.insert(toFixedHex(1337)).should.be.rejected
|
||||||
error.reason.should.be.equal('Merkle tree is full. No more leafs can be added')
|
error.reason.should.be.equal('Merkle tree is full. No more leaves can be added')
|
||||||
|
|
||||||
error = await merkleTreeWithHistory.insert(toFixedHex(1)).should.be.rejected
|
error = await merkleTreeWithHistory.insert(toFixedHex(1)).should.be.rejected
|
||||||
error.reason.should.be.equal('Merkle tree is full. No more leafs can be added')
|
error.reason.should.be.equal('Merkle tree is full. No more leaves can be added')
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('hasher gas', async () => {
|
it.skip('hasher gas', async () => {
|
||||||
|
@ -207,19 +95,16 @@ contract('MerkleTreeWithHistory', accounts => {
|
||||||
|
|
||||||
describe('#isKnownRoot', () => {
|
describe('#isKnownRoot', () => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
let path
|
|
||||||
|
|
||||||
for (let i = 1; i < 5; i++) {
|
for (let i = 1; i < 5; i++) {
|
||||||
await merkleTreeWithHistory.insert(toFixedHex(i), { from: sender }).should.be.fulfilled
|
await merkleTreeWithHistory.insert(toFixedHex(i), { from: sender }).should.be.fulfilled
|
||||||
await tree.insert(i)
|
await tree.insert(i)
|
||||||
path = await tree.path(i - 1)
|
let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(tree.root()))
|
||||||
let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(path.root))
|
|
||||||
isKnown.should.be.equal(true)
|
isKnown.should.be.equal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
await merkleTreeWithHistory.insert(toFixedHex(42), { from: sender }).should.be.fulfilled
|
await merkleTreeWithHistory.insert(toFixedHex(42), { from: sender }).should.be.fulfilled
|
||||||
// check outdated root
|
// check outdated root
|
||||||
let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(path.root))
|
let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(tree.root()))
|
||||||
isKnown.should.be.equal(true)
|
isKnown.should.be.equal(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -230,18 +115,10 @@ contract('MerkleTreeWithHistory', accounts => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await revertSnapshot(snapshotId.result)
|
await revertSnapshot(snapshotId.result)
|
||||||
// eslint-disable-next-line require-atomic-updates
|
// eslint-disable-next-line require-atomic-updates
|
||||||
snapshotId = await takeSnapshot()
|
snapshotId = await takeSnapshot()
|
||||||
hasher = new hasherImpl()
|
tree = new MerkleTree(levels)
|
||||||
tree = new MerkleTree(
|
|
||||||
levels,
|
|
||||||
null,
|
|
||||||
prefix,
|
|
||||||
null,
|
|
||||||
hasher,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
require('dotenv').config()
|
require('dotenv').config()
|
||||||
const HDWalletProvider = require('@truffle/hdwallet-provider')
|
const HDWalletProvider = require('@truffle/hdwallet-provider')
|
||||||
const utils = require('web3-utils')
|
const utils = require('web3-utils')
|
||||||
// const infuraKey = "fj4jll3k.....";
|
|
||||||
//
|
|
||||||
// const fs = require('fs');
|
|
||||||
// const mnemonic = fs.readFileSync(".secret").toString().trim();
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
|
@ -18,47 +14,49 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
networks: {
|
networks: {
|
||||||
// Useful for testing. The `development` name is special - truffle uses it by default
|
|
||||||
// if it's defined here and no other network is specified at the command line.
|
|
||||||
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
|
|
||||||
// tab if you use this network and you must also set the `host`, `port` and `network_id`
|
|
||||||
// options below to some value.
|
|
||||||
|
|
||||||
development: {
|
development: {
|
||||||
host: '127.0.0.1', // Localhost (default: none)
|
host: '127.0.0.1', // Localhost (default: none)
|
||||||
port: 8545, // Standard Ethereum port (default: none)
|
port: 8545, // Standard Ethereum port (default: none)
|
||||||
network_id: '*', // Any network (default: none)
|
network_id: '*', // Any network (default: none)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Another network with more advanced options...
|
|
||||||
// advanced: {
|
|
||||||
// port: 8777, // Custom port
|
|
||||||
// network_id: 1342, // Custom network
|
|
||||||
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
|
|
||||||
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
|
|
||||||
// from: <address>, // Account to send txs from (default: accounts[0])
|
|
||||||
// websockets: true // Enable EventEmitter interface for web3 (default: false)
|
|
||||||
// },
|
|
||||||
|
|
||||||
// Useful for deploying to a public network.
|
|
||||||
// NB: It's important to wrap the provider as a function.
|
|
||||||
kovan: {
|
kovan: {
|
||||||
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'https://kovan.infura.io/v3/97c8bf358b9942a9853fab1ba93dc5b3'),
|
provider: () =>
|
||||||
|
new HDWalletProvider(
|
||||||
|
process.env.PRIVATE_KEY,
|
||||||
|
'https://kovan.infura.io/v3/97c8bf358b9942a9853fab1ba93dc5b3',
|
||||||
|
),
|
||||||
network_id: 42,
|
network_id: 42,
|
||||||
gas: 6000000,
|
gas: 6000000,
|
||||||
gasPrice: utils.toWei('1', 'gwei'),
|
gasPrice: utils.toWei('1', 'gwei'),
|
||||||
// confirmations: 0,
|
// confirmations: 0,
|
||||||
// timeoutBlocks: 200,
|
// timeoutBlocks: 200,
|
||||||
skipDryRun: true
|
skipDryRun: true,
|
||||||
|
},
|
||||||
|
goerli: {
|
||||||
|
provider: () =>
|
||||||
|
new HDWalletProvider(
|
||||||
|
process.env.PRIVATE_KEY,
|
||||||
|
'https://goerli.infura.io/v3/d34c08f2cb7c4111b645d06ac7e35ba8',
|
||||||
|
),
|
||||||
|
network_id: 5,
|
||||||
|
gas: 6000000,
|
||||||
|
gasPrice: utils.toWei('1', 'gwei'),
|
||||||
|
// confirmations: 0,
|
||||||
|
// timeoutBlocks: 200,
|
||||||
|
skipDryRun: true,
|
||||||
},
|
},
|
||||||
rinkeby: {
|
rinkeby: {
|
||||||
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'https://rinkeby.infura.io/v3/97c8bf358b9942a9853fab1ba93dc5b3'),
|
provider: () =>
|
||||||
|
new HDWalletProvider(
|
||||||
|
process.env.PRIVATE_KEY,
|
||||||
|
'https://rinkeby.infura.io/v3/97c8bf358b9942a9853fab1ba93dc5b3',
|
||||||
|
),
|
||||||
network_id: 4,
|
network_id: 4,
|
||||||
gas: 6000000,
|
gas: 6000000,
|
||||||
gasPrice: utils.toWei('1', 'gwei'),
|
gasPrice: utils.toWei('1', 'gwei'),
|
||||||
// confirmations: 0,
|
// confirmations: 0,
|
||||||
// timeoutBlocks: 200,
|
// timeoutBlocks: 200,
|
||||||
skipDryRun: true
|
skipDryRun: true,
|
||||||
},
|
},
|
||||||
mainnet: {
|
mainnet: {
|
||||||
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'http://ethereum-rpc.trustwalletapp.com'),
|
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'http://ethereum-rpc.trustwalletapp.com'),
|
||||||
|
@ -67,18 +65,10 @@ module.exports = {
|
||||||
gasPrice: utils.toWei('2', 'gwei'),
|
gasPrice: utils.toWei('2', 'gwei'),
|
||||||
// confirmations: 0,
|
// confirmations: 0,
|
||||||
// timeoutBlocks: 200,
|
// timeoutBlocks: 200,
|
||||||
skipDryRun: true
|
skipDryRun: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Useful for private networks
|
|
||||||
// private: {
|
|
||||||
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
|
|
||||||
// network_id: 2111, // This network is yours, in the cloud.
|
|
||||||
// production: true // Treats this network as if it was a public net. (default: false)
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Set default mocha options here, use special reporters etc.
|
|
||||||
mocha: {
|
mocha: {
|
||||||
// timeout: 100000
|
// timeout: 100000
|
||||||
},
|
},
|
||||||
|
@ -86,15 +76,23 @@ module.exports = {
|
||||||
// Configure your compilers
|
// Configure your compilers
|
||||||
compilers: {
|
compilers: {
|
||||||
solc: {
|
solc: {
|
||||||
version: '0.5.17', // Fetch exact version from solc-bin (default: truffle's version)
|
version: '0.7.6',
|
||||||
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
|
settings: {
|
||||||
settings: { // See the solidity docs for advice about optimization and evmVersion
|
|
||||||
optimizer: {
|
optimizer: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
runs: 200
|
runs: 200,
|
||||||
},
|
},
|
||||||
// evmVersion: "byzantium"
|
},
|
||||||
}
|
},
|
||||||
}
|
external: {
|
||||||
}
|
command: 'node ./scripts/compileHasher.js',
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
path: './build/Hasher.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: ['solidity-coverage'],
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue