additional eth for the recipient

This commit is contained in:
Alexey 2019-08-27 23:42:24 +03:00
parent 0f5a5df522
commit 9f33aadd9d
14 changed files with 113 additions and 83 deletions

View File

@ -1,6 +1,7 @@
MERKLE_TREE_HEIGHT=16
# in wei
AMOUNT=1000000000000000000
ETH_AMOUNT=100000000000000000
TOKEN_AMOUNT=100000000000000000
EMPTY_ELEMENT=1337
PRIVATE_KEY=
ERC20_TOKEN=

2
.gitignore vendored
View File

@ -94,3 +94,5 @@ typings/
# DynamoDB Local files
.dynamodb/
ERC20Mixer_flat.sol
ETHMixer_flat.sol

View File

@ -41,6 +41,19 @@ spent since it has the same nullifier and it will prevent you from withdrawing y
1. `npx http-server` - serve current dir, you can use any other http server
1. Open `localhost:8080`
## Deploy ETH Tornado Cash
1. `cp .env.example .env`
1. Tune all necessary params
1. `npx truffle migrate --f 2 --to 4`
## Deploy ERC20 Tornado Cash
1. `cp .env.example .env`
1. Tune all necessary params
1. `npx truffle migrate --f 2 --to 3`
1. `npx truffle migrate --f 5`
**Note**. If you want to reuse the same verifier for all the mixers, then after you deployed one of the mixers you should only run 4th or 5th migration for ETH or ERC20 mixers respectively (`--f 4 --to 4` or `--f 5`).
## Credits
Special thanks to @barryWhiteHat and @kobigurk for valuable input,

8
cli.js
View File

@ -13,7 +13,7 @@ const buildGroth16 = require('websnark/src/groth16')
const websnarkUtils = require('websnark/src/utils')
let web3, mixer, circuit, proving_key, groth16
let MERKLE_TREE_HEIGHT, AMOUNT, EMPTY_ELEMENT
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT
const inBrowser = (typeof window !== 'undefined')
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
@ -30,7 +30,7 @@ async function deposit() {
const deposit = createDeposit(rbigint(31), rbigint(31))
console.log('Submitting deposit transaction')
await mixer.methods.deposit('0x' + deposit.commitment.toString(16)).send({ value: AMOUNT, from: (await web3.eth.getAccounts())[0], gas:1e6 })
await mixer.methods.deposit('0x' + deposit.commitment.toString(16)).send({ value: ETH_AMOUNT, from: (await web3.eth.getAccounts())[0], gas:1e6 })
const note = '0x' + deposit.preimage.toString('hex')
console.log('Your note:', note)
@ -103,7 +103,7 @@ async function init() {
circuit = await (await fetch('build/circuits/withdraw.json')).json()
proving_key = await (await fetch('build/circuits/withdraw_proving_key.bin')).arrayBuffer()
MERKLE_TREE_HEIGHT = 16
AMOUNT = 1e18
ETH_AMOUNT = 1e18
EMPTY_ELEMENT = 0
} else {
web3 = new Web3('http://localhost:8545', null, { transactionConfirmationBlocks: 1 })
@ -112,7 +112,7 @@ async function init() {
proving_key = fs.readFileSync('build/circuits/withdraw_proving_key.bin').buffer
require('dotenv').config()
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT
AMOUNT = process.env.AMOUNT
ETH_AMOUNT = process.env.ETH_AMOUNT
EMPTY_ELEMENT = process.env.EMPTY_ELEMENT
}
groth16 = await buildGroth16()

View File

@ -16,38 +16,61 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ERC20Mixer is Mixer {
IERC20 public token;
// mixed token amount
uint256 public tokenDenomination;
// ether value to cover network fee (for relayer) and to have some ETH on a brand new address
uint256 public etherFeeDenomination;
constructor(
address _verifier,
uint256 _transferValue,
uint256 _etherFeeDenomination,
uint8 _merkleTreeHeight,
uint256 _emptyElement,
address payable _operator,
IERC20 _token
) Mixer(_verifier, _transferValue, _merkleTreeHeight, _emptyElement, _operator) public {
IERC20 _token,
uint256 _tokenDenomination
) Mixer(_verifier, _merkleTreeHeight, _emptyElement, _operator) public {
token = _token;
tokenDenomination = _tokenDenomination;
etherFeeDenomination = _etherFeeDenomination;
}
function deposit(uint256 commitment) public {
require(token.transferFrom(msg.sender, address(this), transferValue), "Approve before using");
/**
@dev Deposit funds into the mixer. The caller must send ETH value equal to `etherFeeDenomination` of this mixer.
The caller also has to have at least `tokenDenomination` amount approved for the mixer.
@param commitment the note commitment, which is PedersenHash(nullifier + secret)
*/
function deposit(uint256 commitment) public payable {
require(msg.value == etherFeeDenomination, "Please send `etherFeeDenomination` ETH along with transaction");
require(token.transferFrom(msg.sender, address(this), tokenDenomination), "Approve before using");
_deposit(commitment);
emit Deposit(commitment, next_index - 1, block.timestamp);
}
/**
@dev Withdraw deposit from the mixer. `a`, `b`, and `c` are zkSNARK proof data, and input is an array of circuit public inputs
`input` array consists of:
- merkle root of all deposits in the mixer
- hash of unique deposit nullifier to prevent double spends
- the receiver of funds
- optional fee that goes to the transaction sender (usually a relay)
*/
function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public {
_withdraw(a, b, c, input);
address receiver = address(input[2]);
address payable receiver = address(input[2]);
uint256 fee = input[3];
uint256 nullifierHash = input[1];
require(fee < transferValue, "Fee exceeds transfer value");
token.transfer(receiver, transferValue - fee);
require(fee < etherFeeDenomination, "Fee exceeds transfer value");
receiver.transfer(etherFeeDenomination - fee);
if (fee > 0) {
token.transfer(operator, fee);
operator.transfer(fee);
}
token.transfer(receiver, tokenDenomination);
emit Withdraw(receiver, nullifierHash, fee);
}
}

View File

@ -14,30 +14,45 @@ pragma solidity ^0.5.8;
import "./Mixer.sol";
contract ETHMixer is Mixer {
uint256 public etherDenomination;
constructor(
address _verifier,
uint256 _transferValue,
uint256 _etherDenomination,
uint8 _merkleTreeHeight,
uint256 _emptyElement,
address payable _operator
) Mixer(_verifier, _transferValue, _merkleTreeHeight, _emptyElement, _operator) public {}
) Mixer(_verifier, _merkleTreeHeight, _emptyElement, _operator) public {
etherDenomination = _etherDenomination;
}
/**
@dev Deposit funds into mixer. The caller must send value equal to `etherDenomination` of this mixer.
@param commitment the note commitment, which is PedersenHash(nullifier + secret)
*/
function deposit(uint256 commitment) public payable {
require(msg.value == transferValue, "Please send `transferValue` ETH along with transaction");
require(msg.value == etherDenomination, "Please send `etherDenomination` ETH along with transaction");
_deposit(commitment);
emit Deposit(commitment, next_index - 1, block.timestamp);
}
/**
@dev Withdraw deposit from the mixer. `a`, `b`, and `c` are zkSNARK proof data, and input is an array of circuit public inputs
`input` array consists of:
- merkle root of all deposits in the mixer
- hash of unique deposit nullifier to prevent double spends
- the receiver of funds
- optional fee that goes to the transaction sender (usually a relay)
*/
function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public {
_withdraw(a, b, c, input);
address payable receiver = address(input[2]);
uint256 fee = input[3];
uint256 nullifierHash = input[1];
require(fee < transferValue, "Fee exceeds transfer value");
receiver.transfer(transferValue - fee);
require(fee < etherDenomination, "Fee exceeds transfer value");
receiver.transfer(etherDenomination - fee);
if (fee > 0) {
operator.transfer(fee);
}

View File

@ -18,7 +18,6 @@ contract IVerifier {
}
contract Mixer is MerkleTreeWithHistory {
uint256 public transferValue;
bool public isDepositsEnabled = true;
// operator can disable new deposits in case of emergency
// it also receives a relayer fee
@ -34,24 +33,20 @@ contract Mixer is MerkleTreeWithHistory {
/**
@dev The constructor
@param _verifier the address of SNARK verifier for this contract
@param _transferValue the value for all deposits in this contract in wei
@param _merkleTreeHeight the height of deposits' Merkle Tree
@param _emptyElement default element of the deposits' Merkle Tree
@param _operator operator address (see operator above)
*/
constructor(
address _verifier,
uint256 _transferValue,
uint8 _merkleTreeHeight,
uint256 _emptyElement,
address payable _operator
) MerkleTreeWithHistory(_merkleTreeHeight, _emptyElement) public {
verifier = IVerifier(_verifier);
transferValue = _transferValue;
operator = _operator;
}
/**
@dev Deposit funds into mixer. The caller must send value equal to `transferValue` of this mixer.
@param commitment the note commitment, which is PedersenHash(nullifier + secret)
*/
function _deposit(uint256 commitment) internal {
require(isDepositsEnabled, "deposits disabled");
require(!commitments[commitment], "The commitment has been submitted");
@ -59,14 +54,6 @@ contract Mixer is MerkleTreeWithHistory {
commitments[commitment] = true;
}
/**
@dev Withdraw deposit from the mixer. `a`, `b`, and `c` are zkSNARK proof data, and input is an array of circuit public inputs
`input` array consists of:
- merkle root of all deposits in the mixer
- hash of unique deposit nullifier to prevent double spends
- the receiver of funds
- optional fee that goes to the transaction sender (usually a relay)
*/
function _withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) internal {
uint256 root = input[0];
uint256 nullifierHash = input[1];

View File

@ -7,11 +7,11 @@ const MiMC = artifacts.require('MiMC')
module.exports = function(deployer, network, accounts) {
return deployer.then(async () => {
const { MERKLE_TREE_HEIGHT, AMOUNT, EMPTY_ELEMENT } = process.env
const { MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT } = process.env
const verifier = await Verifier.deployed()
const miMC = await MiMC.deployed()
await ETHMixer.link(MiMC, miMC.address)
const mixer = await deployer.deploy(ETHMixer, verifier.address, AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, accounts[0])
const mixer = await deployer.deploy(ETHMixer, verifier.address, ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, accounts[0])
console.log('ETHMixer\'s address ', mixer.address)
})
}

View File

@ -8,7 +8,7 @@ const ERC20Mock = artifacts.require('ERC20Mock')
module.exports = function(deployer, network, accounts) {
return deployer.then(async () => {
const { MERKLE_TREE_HEIGHT, AMOUNT, EMPTY_ELEMENT, ERC20_TOKEN } = process.env
const { MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT, ERC20_TOKEN, TOKEN_AMOUNT } = process.env
const verifier = await Verifier.deployed()
const miMC = await MiMC.deployed()
await ERC20Mixer.link(MiMC, miMC.address)
@ -20,11 +20,12 @@ module.exports = function(deployer, network, accounts) {
const mixer = await deployer.deploy(
ERC20Mixer,
verifier.address,
AMOUNT,
ETH_AMOUNT,
MERKLE_TREE_HEIGHT,
EMPTY_ELEMENT,
accounts[0],
token
token,
TOKEN_AMOUNT
)
console.log('ERC20Mixer\'s address ', mixer.address)
})

View File

@ -16,7 +16,7 @@
"migrate:dev": "npx truffle migrate --network development --reset",
"browserify": "npx browserify cli.js -o index.js --exclude worker_threads",
"eslint": "npx eslint --ignore-path .gitignore .",
"flat": "truffle-flattener contracts/Mixer.sol > Mixer_flat.sol"
"flat": "truffle-flattener contracts/ETHMixer.sol > ETHMixer_flat.sol contracts/ERC20Mixer.sol > ERC20Mixer_flat.sol"
},
"keywords": [],
"author": "",

View File

@ -5,17 +5,16 @@ require('chai')
.should()
const fs = require('fs')
const { toBN, toHex, randomHex } = require('web3-utils')
const { toBN, toHex } = require('web3-utils')
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
const Mixer = artifacts.require('./ERC20Mixer.sol')
const Token = artifacts.require('./ERC20Mock.sol')
const { AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
const { ETH_AMOUNT, TOKEN_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
const websnarkUtils = require('websnark/src/utils')
const buildGroth16 = require('websnark/src/groth16')
const stringifyBigInts = require('websnark/tools/stringifybigint').stringifyBigInts
const unstringifyBigInts2 = require('snarkjs/src/stringifybigint').unstringifyBigInts
const snarkjs = require('snarkjs')
const bigInt = snarkjs.bigInt
const crypto = require('crypto')
@ -35,15 +34,6 @@ function generateDeposit() {
return deposit
}
// eslint-disable-next-line no-unused-vars
function BNArrayToStringArray(array) {
const arrayToPrint = []
array.forEach(item => {
arrayToPrint.push(item.toString())
})
return arrayToPrint
}
function getRandomReceiver() {
let receiver = rbigint(20)
while (toHex(receiver.toString()).length !== 42) {
@ -52,24 +42,19 @@ function getRandomReceiver() {
return receiver
}
function snarkVerify(proof) {
proof = unstringifyBigInts2(websnarkUtils.fromSolidityInput(proof))
const verification_key = unstringifyBigInts2(require('../build/circuits/withdraw_verification_key.json'))
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
}
contract('Mixer', accounts => {
contract('ERC20Mixer', accounts => {
let mixer
let token
const sender = accounts[0]
const operator = accounts[0]
const levels = MERKLE_TREE_HEIGHT || 16
const zeroValue = EMPTY_ELEMENT || 1337
const value = AMOUNT || '1000000000000000000' // 1 ether
const tokenDenomination = TOKEN_AMOUNT || '1000000000000000000' // 1 ether
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
let snapshotId
let prefix = 'test'
let tree
const fee = bigInt(AMOUNT).shr(1) || bigInt(1e17)
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
const receiver = getRandomReceiver()
const relayer = accounts[1]
let groth16
@ -85,7 +70,7 @@ contract('Mixer', accounts => {
)
mixer = await Mixer.deployed()
token = await Token.deployed()
await token.mint(sender, value)
await token.mint(sender, tokenDenomination)
snapshotId = await takeSnapshot()
groth16 = await buildGroth16()
circuit = require('../build/circuits/withdraw.json')
@ -102,9 +87,9 @@ contract('Mixer', accounts => {
describe('#deposit', () => {
it('should work', async () => {
const commitment = 43
await token.approve(mixer.address, value)
await token.approve(mixer.address, tokenDenomination)
let { logs } = await mixer.deposit(commitment, { from: sender })
let { logs } = await mixer.deposit(commitment, { value, from: sender })
logs[0].event.should.be.equal('Deposit')
logs[0].args.commitment.should.be.eq.BN(toBN(commitment))
@ -117,17 +102,17 @@ contract('Mixer', accounts => {
const deposit = generateDeposit()
const user = accounts[4]
await tree.insert(deposit.commitment)
await token.mint(user, value)
await token.mint(user, tokenDenomination)
const balanceUserBefore = await token.balanceOf(user)
await token.approve(mixer.address, value, { from: user })
await token.approve(mixer.address, tokenDenomination, { from: user })
// Uncomment to measure gas usage
// let gas = await mixer.deposit.estimateGas(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
// console.log('deposit gas:', gas)
await mixer.deposit(toBN(deposit.commitment.toString()), { from: user, gasPrice: '0' })
await mixer.deposit(toBN(deposit.commitment.toString()), { value, from: user, gasPrice: '0' })
const balanceUserAfter = await token.balanceOf(user)
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(value)))
balanceUserAfter.should.be.eq.BN(toBN(balanceUserBefore).sub(toBN(tokenDenomination)))
const { root, path_elements, path_index } = await tree.path(0)
@ -152,8 +137,9 @@ contract('Mixer', accounts => {
const balanceMixerBefore = await token.balanceOf(mixer.address)
const balanceRelayerBefore = await token.balanceOf(relayer)
const balanceOperatorBefore = await token.balanceOf(operator)
const ethBalanceOperatorBefore = await web3.eth.getBalance(operator)
const balanceRecieverBefore = await token.balanceOf(toHex(receiver.toString()))
const ethBalanceRecieverBefore = await web3.eth.getBalance(toHex(receiver.toString()))
let isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
isSpent.should.be.equal(false)
@ -164,13 +150,15 @@ contract('Mixer', accounts => {
const balanceMixerAfter = await token.balanceOf(mixer.address)
const balanceRelayerAfter = await token.balanceOf(relayer)
const balanceOperatorAfter = await token.balanceOf(operator)
const ethBalanceOperatorAfter = await web3.eth.getBalance(operator)
const balanceRecieverAfter = await token.balanceOf(toHex(receiver.toString()))
const ethBalanceRecieverAfter = await web3.eth.getBalance(toHex(receiver.toString()))
const feeBN = toBN(fee.toString())
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(value)))
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
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))
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(value)).sub(feeBN))
logs[0].event.should.be.equal('Withdraw')

View File

@ -9,7 +9,7 @@ const { toBN, toHex, randomHex } = require('web3-utils')
const { takeSnapshot, revertSnapshot } = require('../lib/ganacheHelper')
const Mixer = artifacts.require('./ETHMixer.sol')
const { AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
const websnarkUtils = require('websnark/src/utils')
const buildGroth16 = require('websnark/src/groth16')
@ -57,17 +57,17 @@ function snarkVerify(proof) {
return snarkjs['groth'].isValid(verification_key, proof, proof.publicSignals)
}
contract('Mixer', accounts => {
contract('ETHMixer', accounts => {
let mixer
const sender = accounts[0]
const operator = accounts[0]
const levels = MERKLE_TREE_HEIGHT || 16
const zeroValue = EMPTY_ELEMENT || 1337
const value = AMOUNT || '1000000000000000000' // 1 ether
const value = ETH_AMOUNT || '1000000000000000000' // 1 ether
let snapshotId
let prefix = 'test'
let tree
const fee = bigInt(AMOUNT).shr(1) || bigInt(1e17)
const fee = bigInt(ETH_AMOUNT).shr(1) || bigInt(1e17)
const receiver = getRandomReceiver()
const relayer = accounts[1]
let groth16
@ -90,8 +90,8 @@ contract('Mixer', accounts => {
describe('#constructor', () => {
it('should initialize', async () => {
const transferValue = await mixer.transferValue()
transferValue.should.be.eq.BN(toBN(value))
const etherDenomination = await mixer.etherDenomination()
etherDenomination.should.be.eq.BN(toBN(value))
})
})

View File

@ -12,7 +12,7 @@ const MiMC = artifacts.require('./MiMC.sol')
const MerkleTree = require('../lib/MerkleTree')
const MimcHasher = require('../lib/MiMC')
const { AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
const { ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT } = process.env
// eslint-disable-next-line no-unused-vars
function BNArrayToStringArray(array) {
@ -30,7 +30,7 @@ contract('MerkleTreeWithHistory', accounts => {
let zeroValue = EMPTY_ELEMENT || 1337
const sender = accounts[0]
// eslint-disable-next-line no-unused-vars
const value = AMOUNT || '1000000000000000000'
const value = ETH_AMOUNT || '1000000000000000000'
let snapshotId
let prefix = 'test'
let tree

View File

@ -77,7 +77,7 @@ module.exports = {
// Configure your compilers
compilers: {
solc: {
version: '0.5.10', // Fetch exact version from solc-bin (default: truffle's version)
version: '0.5.11', // 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
optimizer: {