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
|
||||
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",
|
||||
"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.
|
||||
|
||||
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
|
||||
- Deposit gas const: 1088354 (43381 + 50859 * tree_depth)
|
||||
|
||||
- Deposit gas cost: 1088354 (43381 + 50859 \* tree_depth)
|
||||
- Withdraw gas cost: 301233
|
||||
- Circuit Constraints = 28271 (1869 + 1325 * tree_depth)
|
||||
- Circuit Proof time = 10213ms (1071 + 347 * tree_depth)
|
||||
- Circuit Constraints = 28271 (1869 + 1325 \* tree_depth)
|
||||
- Circuit Proof time = 10213ms (1071 + 347 \* tree_depth)
|
||||
- Serverless
|
||||
|
||||
![image](diagram.png)
|
||||
![image](docs/diagram.png)
|
||||
|
||||
## 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?
|
||||
|
||||
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
|
||||
* Smart contract audit https://tornado.cash/Tornado_solidity_audit.pdf
|
||||
* Zk-SNARK circuits audit https://tornado.cash/Tornado_circuit_audit.pdf
|
||||
- Cryptographic review https://tornado.cash/audits/TornadoCash_cryptographic_review_ABDK.pdf
|
||||
- Smart contract audit https://tornado.cash/audits/TornadoCash_contract_audit_ABDK.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
|
||||
|
||||
## Requirements
|
||||
|
||||
1. `node v11.15.0`
|
||||
2. `npm install -g npx`
|
||||
|
||||
## 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. `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. 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
|
||||
|
||||
1. `cp .env.example .env`
|
||||
1. `npm run download`
|
||||
1. `npm run build:contract`
|
||||
|
||||
### Ganache
|
||||
|
||||
1. make sure you complete steps from Initialization
|
||||
1. `ganache-cli -i 1337`
|
||||
1. `npm run migrate:dev`
|
||||
|
@ -67,14 +73,16 @@ Use with command line version. Works for Ganache, Kovan and Mainnet:
|
|||
1. `./cli.js --help`
|
||||
|
||||
### Kovan, Mainnet
|
||||
1. make sure you complete steps from Initialization
|
||||
1. Add `PRIVATE_KEY` to `.env` file
|
||||
1. `./cli.js --help`
|
||||
|
||||
1. Please use https://github.com/tornadocash/tornado-cli
|
||||
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:
|
||||
|
||||
```bash
|
||||
./cli.js deposit ETH 0.1 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448
|
||||
```
|
||||
|
||||
> Your note: tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
|
||||
> Tornado ETH balance is 8.9
|
||||
> 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
|
||||
```
|
||||
|
||||
> Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
|
||||
> Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
|
||||
> Getting current state from tornado contract
|
||||
> Generating SNARK proof
|
||||
> 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 mined in block 17036120
|
||||
> Done
|
||||
|
||||
|
||||
## Deploy ETH Tornado Cash
|
||||
|
||||
1. `cp .env.example .env`
|
||||
1. Tune all necessary params
|
||||
1. `npx truffle migrate --network kovan --reset --f 2 --to 4`
|
||||
|
||||
## Deploy ERC20 Tornado Cash
|
||||
|
||||
1. `cp .env.example .env`
|
||||
1. Tune all necessary params
|
||||
1. `npx truffle migrate --network kovan --reset --f 2 --to 3`
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
1. `npm i`
|
||||
1. `ganache-cli -d`
|
||||
1. `npm run downloadKeys`
|
||||
1. `npm build:contract`
|
||||
1. `npm run download`
|
||||
1. `npm run build:contract`
|
||||
1. `cp .env.example .env`
|
||||
1. `npm run migrate:dev`
|
||||
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
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
* 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
|
||||
*/
|
||||
|
||||
pragma solidity 0.5.17;
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
import "./Tornado.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
|
||||
|
||||
contract ERC20Tornado is Tornado {
|
||||
address public token;
|
||||
using SafeERC20 for IERC20;
|
||||
IERC20 public token;
|
||||
|
||||
constructor(
|
||||
IVerifier _verifier,
|
||||
IHasher _hasher,
|
||||
uint256 _denomination,
|
||||
uint32 _merkleTreeHeight,
|
||||
address _operator,
|
||||
address _token
|
||||
) Tornado(_verifier, _denomination, _merkleTreeHeight, _operator) public {
|
||||
IERC20 _token
|
||||
) Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight) {
|
||||
token = _token;
|
||||
}
|
||||
|
||||
function _processDeposit() internal {
|
||||
function _processDeposit() internal override {
|
||||
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");
|
||||
|
||||
_safeErc20Transfer(_recipient, denomination - _fee);
|
||||
token.safeTransfer(_recipient, denomination - _fee);
|
||||
if (_fee > 0) {
|
||||
_safeErc20Transfer(_relayer, _fee);
|
||||
token.safeTransfer(_relayer, _fee);
|
||||
}
|
||||
|
||||
if (_refund > 0) {
|
||||
(bool success, ) = _recipient.call.value(_refund)("");
|
||||
(bool success, ) = _recipient.call{ value: _refund }("");
|
||||
if (!success) {
|
||||
// let's return _refund back to the relayer
|
||||
_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
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
* 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
|
||||
*/
|
||||
|
||||
pragma solidity 0.5.17;
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
import "./Tornado.sol";
|
||||
|
||||
contract ETHTornado is Tornado {
|
||||
constructor(
|
||||
IVerifier _verifier,
|
||||
IHasher _hasher,
|
||||
uint256 _denomination,
|
||||
uint32 _merkleTreeHeight,
|
||||
address _operator
|
||||
) Tornado(_verifier, _denomination, _merkleTreeHeight, _operator) public {
|
||||
}
|
||||
uint32 _merkleTreeHeight
|
||||
) Tornado(_verifier, _hasher, _denomination, _merkleTreeHeight) {}
|
||||
|
||||
function _processDeposit() internal {
|
||||
function _processDeposit() internal override {
|
||||
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
|
||||
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");
|
||||
|
||||
(bool success, ) = _recipient.call.value(denomination - _fee)("");
|
||||
(bool success, ) = _recipient.call{ value: denomination - _fee }("");
|
||||
require(success, "payment to _recipient did not go thru");
|
||||
if (_fee > 0) {
|
||||
(success, ) = _relayer.call.value(_fee)("");
|
||||
(success, ) = _relayer.call{ value: _fee }("");
|
||||
require(success, "payment to _relayer did not go thru");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,74 @@
|
|||
// 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
|
||||
*/
|
||||
* 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
|
||||
*/
|
||||
|
||||
pragma solidity 0.5.17;
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
library Hasher {
|
||||
function MiMCSponge(uint256 in_xL, uint256 in_xR) public pure returns (uint256 xL, uint256 xR);
|
||||
interface IHasher {
|
||||
function MiMCSponge(uint256 in_xL, uint256 in_xR) external pure returns (uint256 xL, uint256 xR);
|
||||
}
|
||||
|
||||
contract MerkleTreeWithHistory {
|
||||
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
uint256 public constant ZERO_VALUE = 21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE
|
||||
IHasher public immutable hasher;
|
||||
|
||||
uint32 public levels;
|
||||
|
||||
// the following variables are made public for easier testing and debugging and
|
||||
// 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 nextIndex = 0;
|
||||
uint32 public constant ROOT_HISTORY_SIZE = 100;
|
||||
bytes32[ROOT_HISTORY_SIZE] public roots;
|
||||
|
||||
constructor(uint32 _treeLevels) public {
|
||||
require(_treeLevels > 0, "_treeLevels should be greater than zero");
|
||||
require(_treeLevels < 32, "_treeLevels should be less than 32");
|
||||
levels = _treeLevels;
|
||||
constructor(uint32 _levels, IHasher _hasher) {
|
||||
require(_levels > 0, "_levels should be greater than zero");
|
||||
require(_levels < 32, "_levels should be less than 32");
|
||||
levels = _levels;
|
||||
hasher = _hasher;
|
||||
|
||||
bytes32 currentZero = bytes32(ZERO_VALUE);
|
||||
zeros.push(currentZero);
|
||||
filledSubtrees.push(currentZero);
|
||||
|
||||
for (uint32 i = 1; i < levels; i++) {
|
||||
currentZero = hashLeftRight(currentZero, currentZero);
|
||||
zeros.push(currentZero);
|
||||
filledSubtrees.push(currentZero);
|
||||
for (uint32 i = 0; i < _levels; i++) {
|
||||
filledSubtrees[i] = zeros(i);
|
||||
}
|
||||
|
||||
roots[0] = hashLeftRight(currentZero, currentZero);
|
||||
roots[0] = zeros(_levels - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@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(_right) < FIELD_SIZE, "_right should be inside the field");
|
||||
uint256 R = uint256(_left);
|
||||
uint256 C = 0;
|
||||
(R, C) = Hasher.MiMCSponge(R, C);
|
||||
(R, C) = _hasher.MiMCSponge(R, C);
|
||||
R = addmod(R, uint256(_right), FIELD_SIZE);
|
||||
(R, C) = Hasher.MiMCSponge(R, C);
|
||||
(R, C) = _hasher.MiMCSponge(R, C);
|
||||
return bytes32(R);
|
||||
}
|
||||
|
||||
function _insert(bytes32 _leaf) internal returns(uint32 index) {
|
||||
uint32 currentIndex = nextIndex;
|
||||
require(currentIndex != uint32(2)**levels, "Merkle tree is full. No more leafs can be added");
|
||||
nextIndex += 1;
|
||||
function _insert(bytes32 _leaf) internal returns (uint32 index) {
|
||||
uint32 _nextIndex = nextIndex;
|
||||
require(_nextIndex != uint32(2)**levels, "Merkle tree is full. No more leaves can be added");
|
||||
uint32 currentIndex = _nextIndex;
|
||||
bytes32 currentLevelHash = _leaf;
|
||||
bytes32 left;
|
||||
bytes32 right;
|
||||
|
@ -73,32 +76,32 @@ contract MerkleTreeWithHistory {
|
|||
for (uint32 i = 0; i < levels; i++) {
|
||||
if (currentIndex % 2 == 0) {
|
||||
left = currentLevelHash;
|
||||
right = zeros[i];
|
||||
|
||||
right = zeros(i);
|
||||
filledSubtrees[i] = currentLevelHash;
|
||||
} else {
|
||||
left = filledSubtrees[i];
|
||||
right = currentLevelHash;
|
||||
}
|
||||
|
||||
currentLevelHash = hashLeftRight(left, right);
|
||||
|
||||
currentLevelHash = hashLeftRight(hasher, left, right);
|
||||
currentIndex /= 2;
|
||||
}
|
||||
|
||||
currentRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE;
|
||||
roots[currentRootIndex] = currentLevelHash;
|
||||
return nextIndex - 1;
|
||||
uint32 newRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE;
|
||||
currentRootIndex = newRootIndex;
|
||||
roots[newRootIndex] = currentLevelHash;
|
||||
nextIndex = _nextIndex + 1;
|
||||
return _nextIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
@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) {
|
||||
return false;
|
||||
}
|
||||
uint32 i = currentRootIndex;
|
||||
uint32 _currentRootIndex = currentRootIndex;
|
||||
uint32 i = _currentRootIndex;
|
||||
do {
|
||||
if (_root == roots[i]) {
|
||||
return true;
|
||||
|
@ -107,14 +110,51 @@ contract MerkleTreeWithHistory {
|
|||
i = ROOT_HISTORY_SIZE;
|
||||
}
|
||||
i--;
|
||||
} while (i != currentRootIndex);
|
||||
} while (i != _currentRootIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@dev Returns the last root
|
||||
*/
|
||||
function getLastRoot() public view returns(bytes32) {
|
||||
function getLastRoot() public view returns (bytes32) {
|
||||
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 {
|
||||
function() external {
|
||||
fallback() external {
|
||||
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/ERC20Mintable.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
|
||||
|
||||
contract ERC20Mock is ERC20Detailed, ERC20Mintable {
|
||||
constructor() ERC20Detailed("DAIMock", "DAIM", 18) public {
|
||||
contract ERC20Mock is ERC20("DAIMock", "DAIM") {
|
||||
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 {
|
||||
uint public _totalSupply;
|
||||
function totalSupply() public view returns (uint);
|
||||
function balanceOf(address who) public view returns (uint);
|
||||
function transfer(address to, uint value) public;
|
||||
event Transfer(address indexed from, address indexed to, uint value);
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
interface ERC20Basic {
|
||||
function _totalSupply() external returns (uint256);
|
||||
|
||||
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
|
||||
* @dev see https://github.com/ethereum/EIPs/issues/20
|
||||
*/
|
||||
contract IUSDT is ERC20Basic {
|
||||
function allowance(address owner, address spender) public view returns (uint);
|
||||
function transferFrom(address from, address to, uint value) public;
|
||||
function approve(address spender, uint value) public;
|
||||
event Approval(address indexed owner, address indexed spender, uint value);
|
||||
interface IUSDT is ERC20Basic {
|
||||
function allowance(address owner, address spender) external view returns (uint256);
|
||||
|
||||
function transferFrom(
|
||||
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 {
|
||||
|
||||
constructor (uint32 _treeLevels) MerkleTreeWithHistory(_treeLevels) public {}
|
||||
constructor(uint32 _treeLevels, IHasher _hasher) MerkleTreeWithHistory(_treeLevels, _hasher) {}
|
||||
|
||||
function insert(bytes32 _leaf) public {
|
||||
_insert(_leaf);
|
||||
_insert(_leaf);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,31 @@
|
|||
// 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
|
||||
*/
|
||||
* 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
|
||||
*/
|
||||
|
||||
pragma solidity 0.5.17;
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
import "./MerkleTreeWithHistory.sol";
|
||||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||
|
||||
contract IVerifier {
|
||||
function verifyProof(bytes memory _proof, uint256[6] memory _input) public returns(bool);
|
||||
interface IVerifier {
|
||||
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;
|
||||
|
||||
mapping(bytes32 => bool) public nullifierHashes;
|
||||
// we store all commitments just to prevent accidental deposits with the same commitment
|
||||
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 Withdrawal(address to, bytes32 nullifierHash, address indexed relayer, uint256 fee);
|
||||
|
@ -39,19 +33,18 @@ contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
|||
/**
|
||||
@dev The constructor
|
||||
@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 _merkleTreeHeight the height of deposits' Merkle Tree
|
||||
@param _operator operator address (see operator comment above)
|
||||
*/
|
||||
constructor(
|
||||
IVerifier _verifier,
|
||||
IHasher _hasher,
|
||||
uint256 _denomination,
|
||||
uint32 _merkleTreeHeight,
|
||||
address _operator
|
||||
) MerkleTreeWithHistory(_merkleTreeHeight) public {
|
||||
uint32 _merkleTreeHeight
|
||||
) MerkleTreeWithHistory(_merkleTreeHeight, _hasher) {
|
||||
require(_denomination > 0, "denomination should be greater than 0");
|
||||
verifier = _verifier;
|
||||
operator = _operator;
|
||||
denomination = _denomination;
|
||||
}
|
||||
|
||||
|
@ -70,7 +63,7 @@ contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
|||
}
|
||||
|
||||
/** @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
|
||||
|
@ -80,11 +73,25 @@ contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
|||
- the recipient of funds
|
||||
- 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(!nullifierHashes[_nullifierHash], "The note has been already spent");
|
||||
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;
|
||||
_processWithdraw(_recipient, _relayer, _fee, _refund);
|
||||
|
@ -92,33 +99,25 @@ contract Tornado is MerkleTreeWithHistory, ReentrancyGuard {
|
|||
}
|
||||
|
||||
/** @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 */
|
||||
function isSpent(bytes32 _nullifierHash) public view returns(bool) {
|
||||
function isSpent(bytes32 _nullifierHash) public view returns (bool) {
|
||||
return nullifierHashes[_nullifierHash];
|
||||
}
|
||||
|
||||
/** @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);
|
||||
for(uint i = 0; i < _nullifierHashes.length; i++) {
|
||||
for (uint256 i = 0; i < _nullifierHashes.length; i++) {
|
||||
if (isSpent(_nullifierHashes[i])) {
|
||||
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">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Tornado test</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Open dev console!<br>
|
||||
Make sure your Metamask is unlocked and connected to Kovan (or other network you've deployed your contract to)<br>
|
||||
<a href="#" onclick="deposit()">Deposit</a>
|
||||
<a href="#" onclick="withdraw()">Withdraw</a>
|
||||
</p>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Tornado test</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Open dev console!<br />
|
||||
Make sure your Metamask is unlocked and connected to Kovan (or other network you've deployed your
|
||||
contract to)<br />
|
||||
<a href="#" onclick="deposit()">Deposit</a>
|
||||
<a href="#" onclick="withdraw()">Withdraw</a>
|
||||
</p>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</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 */
|
||||
const path = require('path')
|
||||
const Hasher = artifacts.require('Hasher')
|
||||
|
||||
const genContract = require('circomlib/src/mimcsponge_gencontract.js')
|
||||
const Artifactor = require('@truffle/artifactor')
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
module.exports = async function (deployer) {
|
||||
await deployer.deploy(Hasher)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* global artifacts */
|
||||
const Verifier = artifacts.require('Verifier')
|
||||
|
||||
module.exports = function(deployer) {
|
||||
module.exports = function (deployer) {
|
||||
deployer.deploy(Verifier)
|
||||
}
|
||||
|
|
|
@ -2,16 +2,20 @@
|
|||
require('dotenv').config({ path: '../.env' })
|
||||
const ETHTornado = artifacts.require('ETHTornado')
|
||||
const Verifier = artifacts.require('Verifier')
|
||||
const hasherContract = artifacts.require('Hasher')
|
||||
const Hasher = artifacts.require('Hasher')
|
||||
|
||||
|
||||
module.exports = function(deployer, network, accounts) {
|
||||
module.exports = function (deployer) {
|
||||
return deployer.then(async () => {
|
||||
const { MERKLE_TREE_HEIGHT, ETH_AMOUNT } = process.env
|
||||
const verifier = await Verifier.deployed()
|
||||
const hasherInstance = await hasherContract.deployed()
|
||||
await ETHTornado.link(hasherContract, hasherInstance.address)
|
||||
const tornado = await deployer.deploy(ETHTornado, verifier.address, ETH_AMOUNT, MERKLE_TREE_HEIGHT, accounts[0])
|
||||
console.log('ETHTornado\'s address ', tornado.address)
|
||||
const hasher = await Hasher.deployed()
|
||||
const tornado = await deployer.deploy(
|
||||
ETHTornado,
|
||||
verifier.address,
|
||||
hasher.address,
|
||||
ETH_AMOUNT,
|
||||
MERKLE_TREE_HEIGHT,
|
||||
)
|
||||
console.log('ETHTornado address', tornado.address)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,29 +2,27 @@
|
|||
require('dotenv').config({ path: '../.env' })
|
||||
const ERC20Tornado = artifacts.require('ERC20Tornado')
|
||||
const Verifier = artifacts.require('Verifier')
|
||||
const hasherContract = artifacts.require('Hasher')
|
||||
const Hasher = artifacts.require('Hasher')
|
||||
const ERC20Mock = artifacts.require('ERC20Mock')
|
||||
|
||||
|
||||
module.exports = function(deployer, network, accounts) {
|
||||
module.exports = function (deployer) {
|
||||
return deployer.then(async () => {
|
||||
const { MERKLE_TREE_HEIGHT, ERC20_TOKEN, TOKEN_AMOUNT } = process.env
|
||||
const verifier = await Verifier.deployed()
|
||||
const hasherInstance = await hasherContract.deployed()
|
||||
await ERC20Tornado.link(hasherContract, hasherInstance.address)
|
||||
const hasher = await Hasher.deployed()
|
||||
let token = ERC20_TOKEN
|
||||
if(token === '') {
|
||||
if (token === '') {
|
||||
const tokenInstance = await deployer.deploy(ERC20Mock)
|
||||
token = tokenInstance.address
|
||||
}
|
||||
const tornado = await deployer.deploy(
|
||||
ERC20Tornado,
|
||||
verifier.address,
|
||||
hasher.address,
|
||||
TOKEN_AMOUNT,
|
||||
MERKLE_TREE_HEIGHT,
|
||||
accounts[0],
|
||||
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": "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: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",
|
||||
"browserify": "npm run build:browserify",
|
||||
"test": "npx truffle test",
|
||||
|
@ -19,19 +19,23 @@
|
|||
"migrate:kovan": "npx truffle migrate --network kovan --reset",
|
||||
"migrate:rinkeby": "npx truffle migrate --network rinkeby --reset",
|
||||
"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",
|
||||
"download": "node downloadKeys.js"
|
||||
"download": "node scripts/downloadKeys.js",
|
||||
"coverage": "yarn truffle run coverage"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@openzeppelin/contracts": "^2.4.0",
|
||||
"@truffle/artifactor": "^4.0.38",
|
||||
"@openzeppelin/contracts": "^3.4.1",
|
||||
"@truffle/contract": "^4.0.39",
|
||||
"@truffle/hdwallet-provider": "^1.0.24",
|
||||
"axios": "^0.19.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bn-chai": "^1.0.1",
|
||||
"browserify": "^16.5.0",
|
||||
"chai": "^4.2.0",
|
||||
|
@ -40,13 +44,21 @@
|
|||
"circomlib": "git+https://github.com/tornadocash/circomlib.git#c372f14d324d57339c88451834bf2824e73bbdbc",
|
||||
"commander": "^4.1.1",
|
||||
"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",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier-plugin-solidity": "^1.0.0-beta.3",
|
||||
"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",
|
||||
"web3": "^1.2.2",
|
||||
"web3-utils": "^1.2.2",
|
||||
"websnark": "git+https://github.com/tornadocash/websnark.git#2041cfa5fa0b71cd5cca9022a4eeea4afe28c9f7"
|
||||
"web3": "^1.3.4",
|
||||
"web3-utils": "^1.3.4",
|
||||
"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 fs = require('fs')
|
||||
const files = ['withdraw.json', 'withdraw_proving_key.bin', 'Verifier.sol', 'withdraw_verification_key.json']
|
||||
const circuitsPath = 'build/circuits'
|
||||
const contractsPath = 'build/contracts'
|
||||
const circuitsPath = __dirname + '/../build/circuits'
|
||||
const contractsPath = __dirname + '/../build/contracts'
|
||||
|
||||
async function downloadFile({ url, path }) {
|
||||
const writer = fs.createWriteStream(path)
|
||||
|
@ -11,7 +11,7 @@ async function downloadFile({ url, path }) {
|
|||
const response = await axios({
|
||||
url,
|
||||
method: 'GET',
|
||||
responseType: 'stream'
|
||||
responseType: 'stream',
|
||||
})
|
||||
|
||||
response.data.pipe(writer)
|
||||
|
@ -25,16 +25,16 @@ async function downloadFile({ url, path }) {
|
|||
async function main() {
|
||||
const release = await axios.get('https://api.github.com/repos/tornadocash/tornado-core/releases/latest')
|
||||
const { assets } = release.data
|
||||
if (!fs.existsSync(circuitsPath)){
|
||||
if (!fs.existsSync(circuitsPath)) {
|
||||
fs.mkdirSync(circuitsPath, { recursive: true })
|
||||
fs.mkdirSync(contractsPath, { recursive: true })
|
||||
}
|
||||
for(let asset of assets) {
|
||||
for (let asset of assets) {
|
||||
if (files.includes(asset.name)) {
|
||||
console.log(`Downloading ${asset.name} ...`)
|
||||
await downloadFile({
|
||||
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',
|
||||
id: Date.now(),
|
||||
method,
|
||||
params
|
||||
params,
|
||||
}, (err, res) => {
|
||||
return err ? reject(err) : resolve(res)
|
||||
})
|
||||
|
@ -48,5 +48,5 @@ module.exports = {
|
|||
minerStop,
|
||||
minerStart,
|
||||
increaseTime,
|
||||
traceTransaction
|
||||
traceTransaction,
|
||||
}
|
|
@ -10,7 +10,7 @@ const snarkjs = require('snarkjs')
|
|||
const crypto = require('crypto')
|
||||
const circomlib = require('circomlib')
|
||||
const bigInt = snarkjs.bigInt
|
||||
const merkleTree = require('./lib/MerkleTree')
|
||||
const merkleTree = require('fixed-merkle-tree')
|
||||
const Web3 = require('web3')
|
||||
const buildGroth16 = require('websnark/src/groth16')
|
||||
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 */
|
||||
const inBrowser = (typeof window !== 'undefined')
|
||||
let isLocalRPC = false
|
||||
const networks = { '1': 'mainnet', '42': 'kovan' }
|
||||
|
||||
/** Generate random number of specified byte length */
|
||||
const rbigint = nbytes => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||
|
@ -45,7 +44,7 @@ async function printETHBalance({ address, name }) {
|
|||
|
||||
/** Display ERC20 account balance */
|
||||
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
|
||||
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 }
|
||||
deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
|
||||
deposit.commitment = pedersenHash(deposit.preimage)
|
||||
deposit.commitmentHex = toHex(deposit.commitment)
|
||||
deposit.nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
|
||||
deposit.nullifierHex = toHex(deposit.nullifierHash)
|
||||
return deposit
|
||||
}
|
||||
|
||||
|
@ -76,7 +77,7 @@ async function deposit({ currency, amount }) {
|
|||
await printETHBalance({ address: senderAccount, name: 'Sender account' })
|
||||
const value = isLocalRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 })
|
||||
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: senderAccount, name: 'Sender account' })
|
||||
} else { // a token
|
||||
|
@ -84,7 +85,7 @@ async function deposit({ currency, amount }) {
|
|||
await printERC20Balance({ address: senderAccount, name: 'Sender account' })
|
||||
const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
|
||||
const tokenAmount = isLocalRPC ? TOKEN_AMOUNT : fromDecimals({ amount, decimals })
|
||||
if(isLocalRPC) {
|
||||
if (isLocalRPC) {
|
||||
console.log('Minting some test tokens to deposit')
|
||||
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))
|
||||
if (toBN(allowance).lt(toBN(tokenAmount))) {
|
||||
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')
|
||||
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: senderAccount, name: 'Sender account' })
|
||||
}
|
||||
|
@ -125,14 +126,16 @@ async function generateMerkleProof(deposit) {
|
|||
const leafIndex = depositEvent ? depositEvent.returnValues.leafIndex : -1
|
||||
|
||||
// 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()
|
||||
assert(isValidRoot === true, 'Merkle tree is corrupted')
|
||||
assert(isSpent === false, 'The note is already spent')
|
||||
assert(leafIndex >= 0, 'The deposit is not found in the tree')
|
||||
|
||||
// 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 }) {
|
||||
// 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
|
||||
const input = {
|
||||
|
@ -160,8 +163,8 @@ async function generateProof({ deposit, recipient, relayerAddress = 0, fee = 0,
|
|||
// Private snark inputs
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
}
|
||||
|
||||
console.log('Generating SNARK proof')
|
||||
|
@ -176,7 +179,7 @@ async function generateProof({ deposit, recipient, relayerAddress = 0, fee = 0,
|
|||
toHex(input.recipient, 20),
|
||||
toHex(input.relayer, 20),
|
||||
toHex(input.fee),
|
||||
toHex(input.refund)
|
||||
toHex(input.refund),
|
||||
]
|
||||
|
||||
return { proof, args }
|
||||
|
@ -193,6 +196,9 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
|||
}
|
||||
refund = toWei(refund)
|
||||
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 { relayerAddress, netId, gasPrices, ethPrices, relayerServiceFee } = relayerStatus.data
|
||||
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 })
|
||||
|
||||
console.log('Sending withdraw transaction through relay')
|
||||
try{
|
||||
try {
|
||||
const relay = await axios.post(relayerURL + '/relay', { contract: tornado._address, proof, args })
|
||||
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 {
|
||||
console.log(`Transaction submitted through the relay. The transaction hash is ${relay.data.txHash}`)
|
||||
}
|
||||
|
||||
const receipt = await waitForTxReceipt({ txHash: relay.data.txHash })
|
||||
console.log('Transaction mined in block', receipt.blockNumber)
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
console.error(e.response.data.error)
|
||||
} else {
|
||||
|
@ -228,13 +234,13 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
|
|||
|
||||
console.log('Submitting withdraw transaction')
|
||||
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) {
|
||||
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 {
|
||||
console.log(`The transaction hash is ${txHash}`)
|
||||
}
|
||||
}).on('error', function(e){
|
||||
}).on('error', function (e) {
|
||||
console.error('on transactionHash error', e.message)
|
||||
})
|
||||
}
|
||||
|
@ -260,7 +266,7 @@ function fromDecimals({ amount, decimals }) {
|
|||
const comps = ether.split('.')
|
||||
if (comps.length > 2) {
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
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 }) {
|
||||
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))
|
||||
let desiredFee
|
||||
switch (currency) {
|
||||
|
@ -304,10 +360,9 @@ function calculateFee({ gasPrices, currency, amount, refund, ethPrices, relayerS
|
|||
break
|
||||
}
|
||||
default: {
|
||||
desiredFee =
|
||||
expense.add(toBN(refund))
|
||||
.mul(toBN(10 ** decimals))
|
||||
.div(toBN(ethPrices[currency]))
|
||||
desiredFee = expense.add(toBN(refund))
|
||||
.mul(toBN(10 ** decimals))
|
||||
.div(toBN(ethPrices[currency]))
|
||||
desiredFee = desiredFee.add(feePercent)
|
||||
break
|
||||
}
|
||||
|
@ -359,6 +414,60 @@ function parseNote(noteString) {
|
|||
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
|
||||
*/
|
||||
|
@ -379,15 +488,23 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
|
|||
} else {
|
||||
// Initialize from local node
|
||||
web3 = new Web3(rpc, null, { transactionConfirmationBlocks: 1 })
|
||||
contractJson = require('./build/contracts/ETHTornado.json')
|
||||
circuit = require('./build/circuits/withdraw.json')
|
||||
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
||||
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT
|
||||
contractJson = require(__dirname + '/../build/contracts/ETHTornado.json')
|
||||
circuit = require(__dirname + '/../build/circuits/withdraw.json')
|
||||
proving_key = fs.readFileSync(__dirname + '/../build/circuits/withdraw_proving_key.bin').buffer
|
||||
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20
|
||||
ETH_AMOUNT = process.env.ETH_AMOUNT
|
||||
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT
|
||||
PRIVATE_KEY = process.env.PRIVATE_KEY
|
||||
erc20ContractJson = require('./build/contracts/ERC20Mock.json')
|
||||
erc20tornadoJson = require('./build/contracts/ERC20Tornado.json')
|
||||
if (PRIVATE_KEY) {
|
||||
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 = await buildGroth16()
|
||||
|
@ -403,22 +520,12 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
|
|||
senderAccount = (await web3.eth.getAccounts())[0]
|
||||
} else {
|
||||
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]
|
||||
if (!tornadoAddress) {
|
||||
throw new Error()
|
||||
}
|
||||
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')
|
||||
process.exit(1)
|
||||
}
|
||||
|
@ -472,6 +579,34 @@ async function main() {
|
|||
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
|
||||
.command('test')
|
||||
.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'
|
||||
await init({ rpc: program.rpc, 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 })
|
||||
})
|
||||
try {
|
||||
await program.parseAsync(process.argv)
|
||||
process.exit(0)
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
console.log('Error:', e)
|
||||
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 crypto = require('crypto')
|
||||
const circomlib = require('circomlib')
|
||||
const merkleTree = require('./lib/MerkleTree')
|
||||
const merkleTree = require('fixed-merkle-tree')
|
||||
const Web3 = require('web3')
|
||||
const buildGroth16 = require('websnark/src/groth16')
|
||||
const websnarkUtils = require('websnark/src/utils')
|
||||
|
@ -18,13 +18,15 @@ const AMOUNT = '1'
|
|||
// CURRENCY = 'ETH'
|
||||
|
||||
/** 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 */
|
||||
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 */
|
||||
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
|
||||
|
@ -43,7 +45,9 @@ function createDeposit(nullifier, secret) {
|
|||
async function deposit() {
|
||||
const deposit = createDeposit(rbigint(31), rbigint(31))
|
||||
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}`)
|
||||
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 leaves = events
|
||||
.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)
|
||||
|
||||
// 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
|
||||
|
||||
// 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()
|
||||
assert(isValidRoot === true, 'Merkle tree is corrupted')
|
||||
assert(isSpent === false, 'The note is already spent')
|
||||
assert(leafIndex >= 0, 'The deposit is not found in the tree')
|
||||
|
||||
// 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) {
|
||||
// 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
|
||||
const input = {
|
||||
|
@ -127,8 +132,8 @@ async function generateSnarkProof(deposit, recipient) {
|
|||
// Private snark inputs
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
}
|
||||
|
||||
console.log('Generating SNARK proof...')
|
||||
|
@ -141,19 +146,21 @@ async function generateSnarkProof(deposit, recipient) {
|
|||
toHex(input.recipient, 20),
|
||||
toHex(input.relayer, 20),
|
||||
toHex(input.fee),
|
||||
toHex(input.refund)
|
||||
toHex(input.refund),
|
||||
]
|
||||
|
||||
return { proof, args }
|
||||
}
|
||||
|
||||
async function main() {
|
||||
web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL, { timeout: 5 * 60 * 1000 }), null, { transactionConfirmationBlocks: 1 })
|
||||
circuit = require('./build/circuits/withdraw.json')
|
||||
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
|
||||
web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL, { timeout: 5 * 60 * 1000 }), null, {
|
||||
transactionConfirmationBlocks: 1,
|
||||
})
|
||||
circuit = require(__dirname + '/../build/circuits/withdraw.json')
|
||||
proving_key = fs.readFileSync(__dirname + '/../build/circuits/withdraw_proving_key.bin').buffer
|
||||
groth16 = await buildGroth16()
|
||||
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)
|
||||
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
|
||||
// eslint-disable-next-line require-atomic-updates
|
|
@ -1,12 +1,9 @@
|
|||
/* global artifacts, web3, contract */
|
||||
require('chai')
|
||||
.use(require('bn-chai')(web3.utils.BN))
|
||||
.use(require('chai-as-promised'))
|
||||
.should()
|
||||
require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
|
||||
const fs = require('fs')
|
||||
|
||||
const { toBN } = require('web3-utils')
|
||||
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
|
||||
const { takeSnapshot, revertSnapshot } = require('../scripts/ganacheHelper')
|
||||
|
||||
const Tornado = artifacts.require('./ERC20Tornado.sol')
|
||||
const BadRecipient = artifacts.require('./BadRecipient.sol')
|
||||
|
@ -21,11 +18,15 @@ const snarkjs = require('snarkjs')
|
|||
const bigInt = snarkjs.bigInt
|
||||
const crypto = require('crypto')
|
||||
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 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)
|
||||
|
||||
function generateDeposit() {
|
||||
|
@ -38,7 +39,7 @@ function generateDeposit() {
|
|||
return deposit
|
||||
}
|
||||
|
||||
contract('ERC20Tornado', accounts => {
|
||||
contract('ERC20Tornado', (accounts) => {
|
||||
let tornado
|
||||
let token
|
||||
let usdtToken
|
||||
|
@ -48,7 +49,6 @@ contract('ERC20Tornado', accounts => {
|
|||
const levels = MERKLE_TREE_HEIGHT || 16
|
||||
let tokenDenomination = TOKEN_AMOUNT || '1000000000000000000' // 1 ether
|
||||
let snapshotId
|
||||
let prefix = 'test'
|
||||
let tree
|
||||
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
||||
const refund = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
||||
|
@ -59,11 +59,7 @@ contract('ERC20Tornado', accounts => {
|
|||
let proving_key
|
||||
|
||||
before(async () => {
|
||||
tree = new MerkleTree(
|
||||
levels,
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
tree = new MerkleTree(levels)
|
||||
tornado = await Tornado.deployed()
|
||||
if (ERC20_TOKEN) {
|
||||
token = await Token.at(ERC20_TOKEN)
|
||||
|
@ -111,7 +107,7 @@ contract('ERC20Tornado', accounts => {
|
|||
it('should work', async () => {
|
||||
const deposit = generateDeposit()
|
||||
const user = accounts[4]
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
await token.mint(user, tokenDenomination)
|
||||
|
||||
const balanceUserBefore = await token.balanceOf(user)
|
||||
|
@ -124,11 +120,11 @@ contract('ERC20Tornado', accounts => {
|
|||
const balanceUserAfter = await token.balanceOf(user)
|
||||
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
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer,
|
||||
recipient,
|
||||
|
@ -138,20 +134,19 @@ contract('ERC20Tornado', accounts => {
|
|||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||
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 ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent.should.be.equal(false)
|
||||
|
@ -164,23 +159,25 @@ contract('ERC20Tornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||
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))
|
||||
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)))
|
||||
|
||||
logs[0].event.should.be.equal('Withdrawal')
|
||||
|
@ -195,7 +192,7 @@ contract('ERC20Tornado', accounts => {
|
|||
const deposit = generateDeposit()
|
||||
const user = accounts[4]
|
||||
recipient = bigInt(badRecipient.address)
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
await token.mint(user, tokenDenomination)
|
||||
|
||||
const balanceUserBefore = await token.balanceOf(user)
|
||||
|
@ -205,11 +202,11 @@ contract('ERC20Tornado', accounts => {
|
|||
const balanceUserAfter = await token.balanceOf(user)
|
||||
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
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer,
|
||||
recipient,
|
||||
|
@ -219,20 +216,19 @@ contract('ERC20Tornado', accounts => {
|
|||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||
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 ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||
let isSpent = await tornado.isSpent(toFixedHex(input.nullifierHash))
|
||||
isSpent.should.be.equal(false)
|
||||
|
@ -243,23 +239,25 @@ contract('ERC20Tornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const ethBalanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||
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))
|
||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore))
|
||||
ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore))
|
||||
ethBalanceRelayerAfter.should.be.eq.BN(toBN(ethBalanceRelayerBefore))
|
||||
|
||||
logs[0].event.should.be.equal('Withdrawal')
|
||||
|
@ -273,17 +271,16 @@ contract('ERC20Tornado', accounts => {
|
|||
it('should reject with wrong refund value', async () => {
|
||||
const deposit = generateDeposit()
|
||||
const user = accounts[4]
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
await token.mint(user, tokenDenomination)
|
||||
await token.approve(tornado.address, tokenDenomination, { from: user })
|
||||
await tornado.deposit(toFixedHex(deposit.commitment), { from: user, gasPrice: '0' })
|
||||
|
||||
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
const { pathElements, pathIndices } = tree.path(0)
|
||||
// Circuit input
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer,
|
||||
recipient,
|
||||
|
@ -293,11 +290,10 @@ contract('ERC20Tornado', accounts => {
|
|||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
|
@ -307,13 +303,16 @@ contract('ERC20Tornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
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 } = await tornado.withdraw(proof, ...args, { value: toBN(refund).mul(toBN(2)), from: relayer, gasPrice: '0' }).should.be.rejected)
|
||||
;({ reason } = await tornado.withdraw(proof, ...args, {
|
||||
value: toBN(refund).mul(toBN(2)),
|
||||
from: relayer,
|
||||
gasPrice: '0',
|
||||
}).should.be.rejected)
|
||||
reason.should.be.equal('Incorrect refund amount received by the contract')
|
||||
})
|
||||
|
||||
|
@ -329,7 +328,7 @@ contract('ERC20Tornado', accounts => {
|
|||
console.log('userBal', userBal.toString())
|
||||
const senderBal = await usdtToken.balanceOf(sender)
|
||||
console.log('senderBal', senderBal.toString())
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
await usdtToken.transfer(user, tokenDenomination, { from: sender })
|
||||
console.log('transfer done')
|
||||
|
||||
|
@ -345,12 +344,12 @@ contract('ERC20Tornado', accounts => {
|
|||
const balanceUserAfter = await usdtToken.balanceOf(user)
|
||||
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
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
recipient,
|
||||
|
@ -360,19 +359,18 @@ contract('ERC20Tornado', accounts => {
|
|||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceTornadoBefore = await usdtToken.balanceOf(tornado.address)
|
||||
const balanceRelayerBefore = await usdtToken.balanceOf(relayer)
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverBefore = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const balanceReceiverBefore = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
|
@ -385,22 +383,21 @@ contract('ERC20Tornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceTornadoAfter = await usdtToken.balanceOf(tornado.address)
|
||||
const balanceRelayerAfter = await usdtToken.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const balanceReceiverAfter = await usdtToken.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination)))
|
||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(refund)).sub(feeBN))
|
||||
|
||||
balanceReceiverAfter.should.be.eq.BN(toBN(balanceReceiverBefore).add(toBN(tokenDenomination)))
|
||||
ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)).sub(feeBN))
|
||||
|
||||
logs[0].event.should.be.equal('Withdrawal')
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
|
@ -420,7 +417,7 @@ contract('ERC20Tornado', accounts => {
|
|||
console.log('userBal', userBal.toString())
|
||||
const senderBal = await token.balanceOf(sender)
|
||||
console.log('senderBal', senderBal.toString())
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
await token.transfer(user, tokenDenomination, { from: sender })
|
||||
console.log('transfer done')
|
||||
|
||||
|
@ -434,12 +431,12 @@ contract('ERC20Tornado', accounts => {
|
|||
const balanceUserAfter = await token.balanceOf(user)
|
||||
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
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
recipient,
|
||||
|
@ -449,19 +446,18 @@ contract('ERC20Tornado', accounts => {
|
|||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceTornadoBefore = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerBefore = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const balanceReceiverBefore = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceReceiverBefore = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
let isSpent = await tornado.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
|
@ -474,7 +470,7 @@ contract('ERC20Tornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { value: refund, from: relayer, gasPrice: '0' })
|
||||
console.log('withdraw done')
|
||||
|
@ -482,15 +478,14 @@ contract('ERC20Tornado', accounts => {
|
|||
const balanceTornadoAfter = await token.balanceOf(tornado.address)
|
||||
const balanceRelayerAfter = await token.balanceOf(relayer)
|
||||
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
|
||||
const balanceRecieverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceRecieverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const balanceReceiverAfter = await token.balanceOf(toFixedHex(recipient, 20))
|
||||
const ethBalanceReceiverAfter = await web3.eth.getBalance(toFixedHex(recipient, 20))
|
||||
const feeBN = toBN(fee.toString())
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(tokenDenomination)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore).add(feeBN))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination)))
|
||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(refund)).sub(feeBN))
|
||||
|
||||
balanceReceiverAfter.should.be.eq.BN(toBN(balanceReceiverBefore).add(toBN(tokenDenomination)))
|
||||
ethBalanceReceiverAfter.should.be.eq.BN(toBN(ethBalanceReceiverBefore).add(toBN(refund)).sub(feeBN))
|
||||
|
||||
logs[0].event.should.be.equal('Withdrawal')
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
|
@ -505,10 +500,6 @@ contract('ERC20Tornado', accounts => {
|
|||
await revertSnapshot(snapshotId.result)
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
snapshotId = await takeSnapshot()
|
||||
tree = new MerkleTree(
|
||||
levels,
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
tree = new MerkleTree(levels)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
/* global artifacts, web3, contract */
|
||||
require('chai')
|
||||
.use(require('bn-chai')(web3.utils.BN))
|
||||
.use(require('chai-as-promised'))
|
||||
.should()
|
||||
require('chai').use(require('bn-chai')(web3.utils.BN)).use(require('chai-as-promised')).should()
|
||||
const fs = require('fs')
|
||||
|
||||
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 { ETH_AMOUNT, MERKLE_TREE_HEIGHT } = process.env
|
||||
|
@ -19,11 +16,15 @@ const snarkjs = require('snarkjs')
|
|||
const bigInt = snarkjs.bigInt
|
||||
const crypto = require('crypto')
|
||||
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 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)
|
||||
|
||||
function generateDeposit() {
|
||||
|
@ -39,7 +40,7 @@ function generateDeposit() {
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
function BNArrayToStringArray(array) {
|
||||
const arrayToPrint = []
|
||||
array.forEach(item => {
|
||||
array.forEach((item) => {
|
||||
arrayToPrint.push(item.toString())
|
||||
})
|
||||
return arrayToPrint
|
||||
|
@ -51,14 +52,13 @@ function snarkVerify(proof) {
|
|||
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
|
||||
}
|
||||
|
||||
contract('ETHTornado', accounts => {
|
||||
contract('ETHTornado', (accounts) => {
|
||||
let tornado
|
||||
const sender = accounts[0]
|
||||
const operator = accounts[0]
|
||||
const levels = MERKLE_TREE_HEIGHT || 16
|
||||
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
|
||||
let snapshotId
|
||||
let prefix = 'test'
|
||||
let tree
|
||||
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
|
||||
const refund = bigInt(0)
|
||||
|
@ -69,11 +69,7 @@ contract('ETHTornado', accounts => {
|
|||
let proving_key
|
||||
|
||||
before(async () => {
|
||||
tree = new MerkleTree(
|
||||
levels,
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
tree = new MerkleTree(levels)
|
||||
tornado = await Tornado.deployed()
|
||||
snapshotId = await takeSnapshot()
|
||||
groth16 = await buildGroth16()
|
||||
|
@ -97,8 +93,8 @@ contract('ETHTornado', accounts => {
|
|||
logs[0].args.commitment.should.be.equal(commitment)
|
||||
logs[0].args.leafIndex.should.be.eq.BN(0)
|
||||
|
||||
commitment = toFixedHex(12);
|
||||
({ logs } = await tornado.deposit(commitment, { value, from: accounts[2] }))
|
||||
commitment = toFixedHex(12)
|
||||
;({ logs } = await tornado.deposit(commitment, { value, from: accounts[2] }))
|
||||
|
||||
logs[0].event.should.be.equal('Deposit')
|
||||
logs[0].args.commitment.should.be.equal(commitment)
|
||||
|
@ -116,11 +112,11 @@ contract('ETHTornado', accounts => {
|
|||
describe('snark proof verification on js side', () => {
|
||||
it('should detect tampering', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
const { root, path_elements, path_index } = await tree.path(0)
|
||||
tree.insert(deposit.commitment)
|
||||
const { pathElements, pathIndices } = tree.path(0)
|
||||
|
||||
const input = stringifyBigInts({
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
|
@ -128,8 +124,8 @@ contract('ETHTornado', accounts => {
|
|||
fee,
|
||||
refund,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
let proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
|
@ -138,7 +134,8 @@ contract('ETHTornado', accounts => {
|
|||
result.should.be.equal(true)
|
||||
|
||||
// nullifier
|
||||
proofData.publicSignals[1] = '133792158246920651341275668520530514036799294649489851421007411546007850802'
|
||||
proofData.publicSignals[1] =
|
||||
'133792158246920651341275668520530514036799294649489851421007411546007850802'
|
||||
result = snarkVerify(proofData)
|
||||
result.should.be.equal(false)
|
||||
proofData = originalProof
|
||||
|
@ -161,7 +158,7 @@ contract('ETHTornado', accounts => {
|
|||
it('should work', async () => {
|
||||
const deposit = generateDeposit()
|
||||
const user = accounts[4]
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
|
||||
const balanceUserBefore = await web3.eth.getBalance(user)
|
||||
|
||||
|
@ -173,12 +170,12 @@ contract('ETHTornado', accounts => {
|
|||
const balanceUserAfter = await web3.eth.getBalance(user)
|
||||
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
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
recipient,
|
||||
|
@ -188,18 +185,17 @@ contract('ETHTornado', accounts => {
|
|||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
const balanceTornadoBefore = await web3.eth.getBalance(tornado.address)
|
||||
const balanceRelayerBefore = await web3.eth.getBalance(relayer)
|
||||
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))
|
||||
isSpent.should.be.equal(false)
|
||||
|
||||
|
@ -212,20 +208,19 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
const { logs } = await tornado.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
||||
|
||||
const balanceTornadoAfter = await web3.eth.getBalance(tornado.address)
|
||||
const balanceRelayerAfter = await web3.eth.getBalance(relayer)
|
||||
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())
|
||||
balanceTornadoAfter.should.be.eq.BN(toBN(balanceTornadoBefore).sub(toBN(value)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||
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].args.nullifierHash.should.be.equal(toFixedHex(input.nullifierHash))
|
||||
|
@ -237,13 +232,13 @@ contract('ETHTornado', accounts => {
|
|||
|
||||
it('should prevent double spend', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
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({
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
|
@ -251,8 +246,8 @@ contract('ETHTornado', accounts => {
|
|||
fee,
|
||||
refund,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
@ -262,7 +257,7 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
await tornado.withdraw(proof, ...args, { from: relayer }).should.be.fulfilled
|
||||
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 () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
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({
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
|
@ -285,18 +280,22 @@ contract('ETHTornado', accounts => {
|
|||
fee,
|
||||
refund,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
toFixedHex(toBN(input.nullifierHash).add(toBN('21888242871839275222246405745257275088548364400416034343698204186575808495617'))),
|
||||
toFixedHex(
|
||||
toBN(input.nullifierHash).add(
|
||||
toBN('21888242871839275222246405745257275088548364400416034343698204186575808495617'),
|
||||
),
|
||||
),
|
||||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
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 () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
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 input = stringifyBigInts({
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
|
@ -318,8 +317,8 @@ contract('ETHTornado', accounts => {
|
|||
fee: largeFee,
|
||||
refund,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
|
@ -330,7 +329,7 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
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 () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
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({
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
recipient,
|
||||
fee,
|
||||
refund,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
|
@ -365,7 +364,7 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
const error = await tornado.withdraw(proof, ...args, { from: relayer }).should.be.rejected
|
||||
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 () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
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({
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
|
@ -387,8 +386,8 @@ contract('ETHTornado', accounts => {
|
|||
fee,
|
||||
refund,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
let { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
@ -398,7 +397,7 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
let incorrectArgs
|
||||
const originalProof = proof.slice()
|
||||
|
@ -410,7 +409,7 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex('0x0000000000000000000000007a1f9131357404ef86d7c38dbffed2da70321337', 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
let error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Invalid withdraw proof')
|
||||
|
@ -422,7 +421,7 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex('0x000000000000000000000000000000000000000000000000015345785d8a0000'),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Invalid withdraw proof')
|
||||
|
@ -434,7 +433,7 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
error = await tornado.withdraw(proof, ...incorrectArgs, { from: relayer }).should.be.rejected
|
||||
error.reason.should.be.equal('Invalid withdraw proof')
|
||||
|
@ -449,22 +448,22 @@ contract('ETHTornado', accounts => {
|
|||
|
||||
it('should reject with non zero refund', async () => {
|
||||
const deposit = generateDeposit()
|
||||
await tree.insert(deposit.commitment)
|
||||
tree.insert(deposit.commitment)
|
||||
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({
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
recipient,
|
||||
fee,
|
||||
refund: bigInt(1),
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
|
@ -476,74 +475,28 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
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', () => {
|
||||
it('should work', async () => {
|
||||
const deposit1 = generateDeposit()
|
||||
const deposit2 = generateDeposit()
|
||||
await tree.insert(deposit1.commitment)
|
||||
await tree.insert(deposit2.commitment)
|
||||
tree.insert(deposit1.commitment)
|
||||
tree.insert(deposit2.commitment)
|
||||
await tornado.deposit(toFixedHex(deposit1.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
|
||||
const input = stringifyBigInts({
|
||||
// public
|
||||
root,
|
||||
root: tree.root(),
|
||||
nullifierHash: pedersenHash(deposit2.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
recipient,
|
||||
|
@ -553,11 +506,10 @@ contract('ETHTornado', accounts => {
|
|||
// private
|
||||
nullifier: deposit2.nullifier,
|
||||
secret: deposit2.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndices: path_index,
|
||||
pathElements: pathElements,
|
||||
pathIndices: pathIndices,
|
||||
})
|
||||
|
||||
|
||||
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { proof } = websnarkUtils.toSolidityInput(proofData)
|
||||
|
||||
|
@ -567,7 +519,7 @@ contract('ETHTornado', accounts => {
|
|||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.refund)
|
||||
toFixedHex(input.refund),
|
||||
]
|
||||
|
||||
await tornado.withdraw(proof, ...args, { from: relayer, gasPrice: '0' })
|
||||
|
@ -583,10 +535,6 @@ contract('ETHTornado', accounts => {
|
|||
await revertSnapshot(snapshotId.result)
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
snapshotId = await takeSnapshot()
|
||||
tree = new MerkleTree(
|
||||
levels,
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
tree = new MerkleTree(levels)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
/* global artifacts, web3, contract, assert */
|
||||
require('chai')
|
||||
.use(require('bn-chai')(web3.utils.BN))
|
||||
.use(require('chai-as-promised'))
|
||||
.should()
|
||||
/* global artifacts, web3, contract */
|
||||
require('chai').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 hasherContract = artifacts.require('./Hasher.sol')
|
||||
|
||||
const MerkleTree = require('../lib/MerkleTree')
|
||||
const hasherImpl = require('../lib/MiMC')
|
||||
const MerkleTree = require('fixed-merkle-tree')
|
||||
|
||||
const snarkjs = require('snarkjs')
|
||||
const bigInt = snarkjs.bigInt
|
||||
|
@ -20,7 +16,7 @@ const { ETH_AMOUNT, MERKLE_TREE_HEIGHT } = process.env
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
function BNArrayToStringArray(array) {
|
||||
const arrayToPrint = []
|
||||
array.forEach(item => {
|
||||
array.forEach((item) => {
|
||||
arrayToPrint.push(item.toString())
|
||||
})
|
||||
return arrayToPrint
|
||||
|
@ -33,7 +29,7 @@ function toFixedHex(number, length = 32) {
|
|||
return str
|
||||
}
|
||||
|
||||
contract('MerkleTreeWithHistory', accounts => {
|
||||
contract('MerkleTreeWithHistory', (accounts) => {
|
||||
let merkleTreeWithHistory
|
||||
let hasherInstance
|
||||
let levels = MERKLE_TREE_HEIGHT || 16
|
||||
|
@ -41,19 +37,12 @@ contract('MerkleTreeWithHistory', accounts => {
|
|||
// eslint-disable-next-line no-unused-vars
|
||||
const value = ETH_AMOUNT || '1000000000000000000'
|
||||
let snapshotId
|
||||
let prefix = 'test'
|
||||
let tree
|
||||
let hasher
|
||||
|
||||
before(async () => {
|
||||
tree = new MerkleTree(
|
||||
levels,
|
||||
null,
|
||||
prefix,
|
||||
)
|
||||
tree = new MerkleTree(levels)
|
||||
hasherInstance = await hasherContract.deployed()
|
||||
await MerkleTreeWithHistory.link(hasherContract, hasherInstance.address)
|
||||
merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels)
|
||||
merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, hasherInstance.address)
|
||||
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', () => {
|
||||
it('should insert', async () => {
|
||||
let rootFromContract
|
||||
|
||||
for (let i = 1; i < 11; i++) {
|
||||
await merkleTreeWithHistory.insert(toFixedHex(i), { from: sender })
|
||||
await tree.insert(i)
|
||||
let { root } = await tree.path(i - 1)
|
||||
tree.insert(i)
|
||||
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 () => {
|
||||
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++) {
|
||||
await merkleTreeWithHistory.insert(toFixedHex(i+42)).should.be.fulfilled
|
||||
for (let i = 0; i < 2 ** levels; i++) {
|
||||
await merkleTreeWithHistory.insert(toFixedHex(i + 42)).should.be.fulfilled
|
||||
}
|
||||
|
||||
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.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 () => {
|
||||
|
@ -207,19 +95,16 @@ contract('MerkleTreeWithHistory', accounts => {
|
|||
|
||||
describe('#isKnownRoot', () => {
|
||||
it('should work', async () => {
|
||||
let path
|
||||
|
||||
for (let i = 1; i < 5; i++) {
|
||||
await merkleTreeWithHistory.insert(toFixedHex(i), { from: sender }).should.be.fulfilled
|
||||
await tree.insert(i)
|
||||
path = await tree.path(i - 1)
|
||||
let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(path.root))
|
||||
let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(tree.root()))
|
||||
isKnown.should.be.equal(true)
|
||||
}
|
||||
|
||||
await merkleTreeWithHistory.insert(toFixedHex(42), { from: sender }).should.be.fulfilled
|
||||
// check outdated root
|
||||
let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(path.root))
|
||||
let isKnown = await merkleTreeWithHistory.isKnownRoot(toFixedHex(tree.root()))
|
||||
isKnown.should.be.equal(true)
|
||||
})
|
||||
|
||||
|
@ -230,18 +115,10 @@ contract('MerkleTreeWithHistory', accounts => {
|
|||
})
|
||||
})
|
||||
|
||||
|
||||
afterEach(async () => {
|
||||
await revertSnapshot(snapshotId.result)
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
snapshotId = await takeSnapshot()
|
||||
hasher = new hasherImpl()
|
||||
tree = new MerkleTree(
|
||||
levels,
|
||||
null,
|
||||
prefix,
|
||||
null,
|
||||
hasher,
|
||||
)
|
||||
tree = new MerkleTree(levels)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
require('dotenv').config()
|
||||
const HDWalletProvider = require('@truffle/hdwallet-provider')
|
||||
const utils = require('web3-utils')
|
||||
// const infuraKey = "fj4jll3k.....";
|
||||
//
|
||||
// const fs = require('fs');
|
||||
// const mnemonic = fs.readFileSync(".secret").toString().trim();
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
|
@ -18,47 +14,49 @@ module.exports = {
|
|||
*/
|
||||
|
||||
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: {
|
||||
host: '127.0.0.1', // Localhost (default: none)
|
||||
port: 8545, // Standard Ethereum port (default: none)
|
||||
network_id: '*', // Any network (default: none)
|
||||
host: '127.0.0.1', // Localhost (default: none)
|
||||
port: 8545, // Standard Ethereum port (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: {
|
||||
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,
|
||||
gas: 6000000,
|
||||
gasPrice: utils.toWei('1', 'gwei'),
|
||||
// confirmations: 0,
|
||||
// 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: {
|
||||
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,
|
||||
gas: 6000000,
|
||||
gasPrice: utils.toWei('1', 'gwei'),
|
||||
// confirmations: 0,
|
||||
// timeoutBlocks: 200,
|
||||
skipDryRun: true
|
||||
skipDryRun: true,
|
||||
},
|
||||
mainnet: {
|
||||
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'http://ethereum-rpc.trustwalletapp.com'),
|
||||
|
@ -67,18 +65,10 @@ module.exports = {
|
|||
gasPrice: utils.toWei('2', 'gwei'),
|
||||
// confirmations: 0,
|
||||
// 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: {
|
||||
// timeout: 100000
|
||||
},
|
||||
|
@ -86,15 +76,23 @@ module.exports = {
|
|||
// Configure your compilers
|
||||
compilers: {
|
||||
solc: {
|
||||
version: '0.5.17', // Fetch exact version from solc-bin (default: truffle's version)
|
||||
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
|
||||
settings: { // See the solidity docs for advice about optimization and evmVersion
|
||||
version: '0.7.6',
|
||||
settings: {
|
||||
optimizer: {
|
||||
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