mirror of
https://github.com/tornadocash/tornado-core.git
synced 2025-01-23 02:31:01 -05:00
merge and add cli.js erc20 commands
This commit is contained in:
commit
a64f41a44e
@ -2,7 +2,7 @@ MERKLE_TREE_HEIGHT=16
|
||||
# in wei
|
||||
ETH_AMOUNT=100000000000000000
|
||||
TOKEN_AMOUNT=100000000000000000
|
||||
EMPTY_ELEMENT=1337
|
||||
EMPTY_ELEMENT=1
|
||||
PRIVATE_KEY=
|
||||
ERC20_TOKEN=
|
||||
|
||||
|
50
README.md
50
README.md
@ -1,20 +1,29 @@
|
||||
# Tornado mixer [![Build Status](https://travis-ci.org/peppersec/tornado-mixer.svg?branch=master)](https://travis-ci.org/peppersec/tornado-mixer)
|
||||
|
||||
![mixer image](./mixer.png)
|
||||
Tornado is a non-custodial Ethereum and ERC20 mixer 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.
|
||||
|
||||
To make a deposit user generates a secret and sends its hash (called a commitment) along with 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 withdraw. 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 to do that 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 comes from.
|
||||
|
||||
You can read more about it in [this medium article](https://medium.com/@tornado.cash.mixer/introducing-private-transactions-on-ethereum-now-42ee915babe0)
|
||||
|
||||
## Specs
|
||||
- Deposit gas cost: deposit 888054
|
||||
- Deposit gas const: 888054 (43381 + 50859 * tree_depth)
|
||||
- Withdraw gas cost: 692133
|
||||
- Circuit constraints: 22617
|
||||
- Circuit proving time: 6116ms
|
||||
- Circuit Constraints = 22617 (1869 + 1325 * tree_depth)
|
||||
- Circuit Proof time = 6116ms (1071 + 347 * tree_depth)
|
||||
- Serverless
|
||||
|
||||
![mixer image](./mixer.png)
|
||||
|
||||
## Security risks
|
||||
* Cryptographic tools used by mixer (zkSNARKS, Pedersen commitment, MiMC hash) are not yet extensively audited by cryptographic experts and may be vulnerable
|
||||
* Note: we use MiMC hash only for merkle tree, so even if a preimage attack on MiMC is discovered, it will not allow to deanonymize users. To drain funds attacker needs to be able to generate arbitrary hash collisions, which is a pretty strong assumption.
|
||||
* Bugs in contract. Even though we have an extensive experience in smart contract security audits, we can still make mistakes. An external audit is needed to reduce probablility of bugs. Our mixer is currently being audited, stay tuned.
|
||||
* Relayer is frontrunnable. When relayer submits a transaction someone can see it in tx pool and frontrun it with higher gas price to get the fee and drain relayer funds.
|
||||
* Workaround: we can set high gas price so that (almost) all fee is used on gas. The relayer will not receive profit this way, but this approach is acceptable until we develop more sophisticated system that prevents frontrunning
|
||||
* Bugs in contract. Even though we have an extensive experience in smart contract security audits, we can still make mistakes. An external audit is needed to reduce probablility of bugs
|
||||
* Workaround: we can set high gas price so that (almost) all fee is used on gas
|
||||
* Second workaround: allow only single hardcoded relayer, we use this approach for now
|
||||
* ~~Nullifier griefing. when you submit a withdraw transaction you reveal the nullifier for your note. If someone manages to
|
||||
make a deposit with the same nullifier and withdraw it while your transaction is still in tx pool, your note will be considered
|
||||
spent since it has the same nullifier and it will prevent you from withdrawing your funds~~
|
||||
@ -25,21 +34,38 @@ spent since it has the same nullifier and it will prevent you from withdrawing y
|
||||
2. `npm install -g npx`
|
||||
|
||||
## Usage
|
||||
1. `npm i`
|
||||
|
||||
You can see example usage in cli.js, it works both in console and in browser.
|
||||
|
||||
1. `npm install`
|
||||
1. `cp .env.example .env`
|
||||
1. `npm run build:circuit` - may take 10 minutes or more
|
||||
1. `npm run build:circuit` - this may take 10 minutes or more
|
||||
1. `npm run build:contract`
|
||||
1. `npm run browserify`
|
||||
1. `npx ganache-cli`
|
||||
1. `npm run test` - optionally run tests. It may fail for the first time, just run one more time.
|
||||
|
||||
Use browser version on Kovan:
|
||||
|
||||
1. `vi .env` - add your Kovan private key to deploy contracts
|
||||
1. `npm run migrate`
|
||||
1. `npx http-server` - serve current dir, you can use any other static http server
|
||||
1. Open `localhost:8080`
|
||||
|
||||
Use with command line version with Ganache:
|
||||
### ETHMixer
|
||||
1. `npm run migrate:dev`
|
||||
1. `./cli.js deposit`
|
||||
1. `./cli.js withdraw <note from previous step> <destination eth address>`
|
||||
1. `./cli.js balance <destination eth address>`
|
||||
1. `vi .env` - add your Kovan private key to deploy contracts
|
||||
1. `npm run migrate`
|
||||
1. `npx http-server` - serve current dir, you can use any other http server
|
||||
1. Open `localhost:8080`
|
||||
|
||||
### ERC20Mixer
|
||||
1. `npm run migrate:dev`
|
||||
1. `./cli.js depositErc20`
|
||||
1. `./cli.js withdraw <note from previous step> <destination eth address> <relayer eth address>`
|
||||
1. `./cli.js balanceErc20 <destination eth address> <relayer eth address>`
|
||||
|
||||
If you want, you can point the app to existing tornado contracts on Mainnet or Kovan, it should work without any changes
|
||||
|
||||
## Deploy ETH Tornado Cash
|
||||
1. `cp .env.example .env`
|
||||
|
@ -31,6 +31,7 @@ template Withdraw(levels, rounds) {
|
||||
signal input root;
|
||||
signal input nullifierHash;
|
||||
signal input receiver; // not taking part in any computations
|
||||
signal input relayer; // not taking part in any computations
|
||||
signal input fee; // not taking part in any computations
|
||||
signal private input nullifier;
|
||||
signal private input secret;
|
||||
@ -56,8 +57,10 @@ template Withdraw(levels, rounds) {
|
||||
// Squares are used to prevent optimizer from removing those constraints
|
||||
signal receiverSquare;
|
||||
signal feeSquare;
|
||||
signal relayerSquare;
|
||||
receiverSquare <== receiver * receiver;
|
||||
feeSquare <== fee * fee;
|
||||
relayerSquare <== relayer * relayer;
|
||||
}
|
||||
|
||||
component main = Withdraw(16, 220);
|
||||
|
108
cli.js
108
cli.js
@ -16,9 +16,15 @@ let web3, mixer, erc20mixer, circuit, proving_key, groth16, erc20
|
||||
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT, ERC20_TOKEN
|
||||
const inBrowser = (typeof window !== 'undefined')
|
||||
|
||||
/** Generate random number of specified byte length */
|
||||
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
|
||||
|
||||
/** Compute pedersen hash */
|
||||
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
|
||||
|
||||
/**
|
||||
* Create deposit object from secret and nullifier
|
||||
*/
|
||||
function createDeposit(nullifier, secret) {
|
||||
let deposit = { nullifier, secret }
|
||||
deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
|
||||
@ -26,6 +32,10 @@ function createDeposit(nullifier, secret) {
|
||||
return deposit
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a deposit
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function deposit() {
|
||||
const deposit = createDeposit(rbigint(31), rbigint(31))
|
||||
|
||||
@ -41,10 +51,10 @@ async function depositErc20() {
|
||||
const account = (await web3.eth.getAccounts())[0]
|
||||
const tokenAmount = process.env.TOKEN_AMOUNT
|
||||
await erc20.methods.mint(account, tokenAmount).send({ from: account, gas:1e6 })
|
||||
const allowance = await erc20.methods.allowance(account, erc20mixer.address).call()
|
||||
console.log('erc20mixer allowance', allowance.toString(10))
|
||||
|
||||
await erc20.methods.approve(erc20mixer.address, tokenAmount).send({ from: account, gas:1e6 })
|
||||
const allowance = await erc20.methods.allowance(account, erc20mixer.address).call()
|
||||
console.log('erc20mixer allowance', allowance.toString(10))
|
||||
|
||||
const deposit = createDeposit(rbigint(31), rbigint(31))
|
||||
await erc20mixer.methods.deposit('0x' + deposit.commitment.toString(16)).send({ value: ETH_AMOUNT, from: account, gas:1e6 })
|
||||
@ -56,7 +66,7 @@ async function depositErc20() {
|
||||
return note
|
||||
}
|
||||
|
||||
async function withdrawErc20(note, receiver) {
|
||||
async function withdrawErc20(note, receiver, relayer) {
|
||||
let buf = Buffer.from(note.slice(2), 'hex')
|
||||
let deposit = createDeposit(bigInt.leBuff2int(buf.slice(0, 31)), bigInt.leBuff2int(buf.slice(31, 62)))
|
||||
|
||||
@ -77,7 +87,7 @@ async function withdrawErc20(note, receiver) {
|
||||
const validRoot = await erc20mixer.methods.isKnownRoot(await tree.root()).call()
|
||||
const nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
|
||||
const nullifierHashToCheck = nullifierHash.toString(16).padStart('66', '0x000000')
|
||||
const isSpent = await mixer.methods.isSpent(nullifierHashToCheck).call()
|
||||
const isSpent = await erc20mixer.methods.isSpent(nullifierHashToCheck).call()
|
||||
assert(validRoot === true)
|
||||
assert(isSpent === false)
|
||||
|
||||
@ -89,7 +99,8 @@ async function withdrawErc20(note, receiver) {
|
||||
root: root,
|
||||
nullifierHash,
|
||||
receiver: bigInt(receiver),
|
||||
fee: bigInt(0),
|
||||
relayer: bigInt(relayer),
|
||||
fee: bigInt(web3.utils.toWei('0.01')),
|
||||
|
||||
// private
|
||||
nullifier: deposit.nullifier,
|
||||
@ -105,7 +116,7 @@ async function withdrawErc20(note, receiver) {
|
||||
console.timeEnd('Proof time')
|
||||
|
||||
console.log('Submitting withdraw transaction')
|
||||
await mixer.methods.withdraw(pi_a, pi_b, pi_c, publicSignals).send({ from: (await web3.eth.getAccounts())[0], gas: 1e6 })
|
||||
await erc20mixer.methods.withdraw(pi_a, pi_b, pi_c, publicSignals).send({ from: (await web3.eth.getAccounts())[0], gas: 1e6 })
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
@ -114,42 +125,58 @@ async function getBalance(receiver) {
|
||||
console.log('Balance is ', web3.utils.fromWei(balance))
|
||||
}
|
||||
|
||||
async function getBalanceErc20(receiver, relayer) {
|
||||
const balanceReceiver = await web3.eth.getBalance(receiver)
|
||||
const balanceRelayer = await web3.eth.getBalance(relayer)
|
||||
const tokenBalanceReceiver = await erc20.methods.balanceOf(receiver).call()
|
||||
const tokenBalanceRelayer = await erc20.methods.balanceOf(relayer).call()
|
||||
console.log('Receiver eth Balance is ', web3.utils.fromWei(balanceReceiver))
|
||||
console.log('Relayer eth Balance is ', web3.utils.fromWei(balanceRelayer))
|
||||
|
||||
console.log('Receiver token Balance is ', web3.utils.fromWei(tokenBalanceReceiver.toString()))
|
||||
console.log('Relayer token Balance is ', web3.utils.fromWei(tokenBalanceRelayer.toString()))
|
||||
}
|
||||
|
||||
async function withdraw(note, receiver) {
|
||||
// Decode hex string and restore the deposit object
|
||||
let buf = Buffer.from(note.slice(2), 'hex')
|
||||
let deposit = createDeposit(bigInt.leBuff2int(buf.slice(0, 31)), bigInt.leBuff2int(buf.slice(31, 62)))
|
||||
const nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
|
||||
const paddedNullifierHash = nullifierHash.toString(16).padStart('66', '0x000000')
|
||||
const paddedCommitment = deposit.commitment.toString(16).padStart('66', '0x000000')
|
||||
|
||||
// Get all deposit events from smart contract and assemble merkle tree from them
|
||||
console.log('Getting current state from mixer contract')
|
||||
const events = await mixer.getPastEvents('Deposit', { fromBlock: mixer.deployedBlock, toBlock: 'latest' })
|
||||
let leafIndex
|
||||
|
||||
const commitment = deposit.commitment.toString(16).padStart('66', '0x000000')
|
||||
const leaves = events
|
||||
.sort((a, b) => a.returnValues.leafIndex.sub(b.returnValues.leafIndex))
|
||||
.map(e => {
|
||||
if (e.returnValues.commitment.eq(commitment)) {
|
||||
leafIndex = e.returnValues.leafIndex.toNumber()
|
||||
}
|
||||
return e.returnValues.commitment
|
||||
})
|
||||
.sort((a, b) => a.returnValues.leafIndex.sub(b.returnValues.leafIndex)) // Sort events in chronological order
|
||||
.map(e => e.returnValues.commitment)
|
||||
const tree = new merkleTree(MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, leaves)
|
||||
const validRoot = await mixer.methods.isKnownRoot(await tree.root()).call()
|
||||
const nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
|
||||
const nullifierHashToCheck = nullifierHash.toString(16).padStart('66', '0x000000')
|
||||
const isSpent = await mixer.methods.isSpent(nullifierHashToCheck).call()
|
||||
assert(validRoot === true)
|
||||
assert(isSpent === false)
|
||||
|
||||
assert(leafIndex >= 0)
|
||||
// Find current commitment in the tree
|
||||
let depositEvent = events.find(e => e.returnValues.commitment.eq(paddedCommitment))
|
||||
let leafIndex = depositEvent ? depositEvent.returnValues.leafIndex.toNumber() : -1
|
||||
|
||||
// Validate that our data is correct
|
||||
const isValidRoot = await mixer.methods.isKnownRoot(await tree.root()).call()
|
||||
const isSpent = await mixer.methods.isSpent(paddedNullifierHash).call()
|
||||
assert(isValidRoot === true) // Merkle tree assembled correctly
|
||||
assert(isSpent === false) // The note is not spent
|
||||
assert(leafIndex >= 0) // Our deposit is present in the tree
|
||||
|
||||
// Compute merkle proof of our commitment
|
||||
const { root, path_elements, path_index } = await tree.path(leafIndex)
|
||||
// Circuit input
|
||||
|
||||
// Prepare circuit input
|
||||
const input = {
|
||||
// public
|
||||
// Public snark inputs
|
||||
root: root,
|
||||
nullifierHash,
|
||||
receiver: bigInt(receiver),
|
||||
relayer: bigInt(0),
|
||||
fee: bigInt(0),
|
||||
|
||||
// private
|
||||
// Private snark inputs
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
@ -167,17 +194,23 @@ async function withdraw(note, receiver) {
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
/**
|
||||
* Init web3, contracts, and snark
|
||||
*/
|
||||
async function init() {
|
||||
let contractJson, erc20ContractJson, erc20mixerJson
|
||||
if (inBrowser) {
|
||||
// Initialize using injected web3 (Metamask)
|
||||
// To assemble web version run `npm run browserify`
|
||||
web3 = new Web3(window.web3.currentProvider, null, { transactionConfirmationBlocks: 1 })
|
||||
contractJson = await (await fetch('build/contracts/ETHMixer.json')).json()
|
||||
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
|
||||
ETH_AMOUNT = 1e18
|
||||
EMPTY_ELEMENT = 0
|
||||
EMPTY_ELEMENT = 1
|
||||
} else {
|
||||
// Initialize from local node
|
||||
web3 = new Web3('http://localhost:8545', null, { transactionConfirmationBlocks: 1 })
|
||||
contractJson = require('./build/contracts/ETHMixer.json')
|
||||
circuit = require('./build/circuits/withdraw.json')
|
||||
@ -192,9 +225,11 @@ async function init() {
|
||||
}
|
||||
groth16 = await buildGroth16()
|
||||
let netId = await web3.eth.net.getId()
|
||||
// const tx = await web3.eth.getTransaction(contractJson.networks[netId].transactionHash)
|
||||
// mixer = new web3.eth.Contract(contractJson.abi, contractJson.networks[netId].address)
|
||||
// mixer.deployedBlock = tx.blockNumber
|
||||
if (contractJson.networks[netId]) {
|
||||
const tx = await web3.eth.getTransaction(contractJson.networks[netId].transactionHash)
|
||||
mixer = new web3.eth.Contract(contractJson.abi, contractJson.networks[netId].address)
|
||||
mixer.deployedBlock = tx.blockNumber
|
||||
}
|
||||
|
||||
const tx3 = await web3.eth.getTransaction(erc20mixerJson.networks[netId].transactionHash)
|
||||
erc20mixer = new web3.eth.Contract(erc20mixerJson.abi, erc20mixerJson.networks[netId].address)
|
||||
@ -265,6 +300,12 @@ if (inBrowser) {
|
||||
} else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'balanceErc20':
|
||||
if (args.length === 3 && /^0x[0-9a-fA-F]{40}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) {
|
||||
init().then(() => getBalanceErc20(args[1], args[2])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
} else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'withdraw':
|
||||
if (args.length === 3 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) {
|
||||
init().then(() => withdraw(args[1], args[2])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
@ -272,6 +313,13 @@ if (inBrowser) {
|
||||
else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'withdrawErc20':
|
||||
if (args.length === 4 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2]) && /^0x[0-9a-fA-F]{40}$/.test(args[3])) {
|
||||
init().then(() => withdrawErc20(args[1], args[2], args[3])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
}
|
||||
else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'auto':
|
||||
if (args.length === 1) {
|
||||
(async () => {
|
||||
|
@ -36,12 +36,12 @@ contract ERC20Mixer is Mixer {
|
||||
safeErc20TransferFrom(msg.sender, address(this), mixDenomination);
|
||||
}
|
||||
|
||||
function _processWithdraw(address payable _receiver, uint256 _fee) internal {
|
||||
function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal {
|
||||
_receiver.transfer(userEther);
|
||||
|
||||
safeErc20Transfer(_receiver, mixDenomination - _fee);
|
||||
if (_fee > 0) {
|
||||
safeErc20Transfer(operator, _fee);
|
||||
safeErc20Transfer(_relayer, _fee);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +56,8 @@ contract ERC20Mixer is Mixer {
|
||||
)
|
||||
);
|
||||
require(success, "not enough allowed tokens");
|
||||
|
||||
// if contract returns some data let's make sure that is `true` according to standard
|
||||
if (data.length > 0) {
|
||||
assembly {
|
||||
success := mload(add(data, 0x20))
|
||||
@ -75,6 +77,8 @@ contract ERC20Mixer is Mixer {
|
||||
)
|
||||
);
|
||||
require(success, "not enough tokens");
|
||||
|
||||
// if contract returns some data let's make sure that is `true` according to standard
|
||||
if (data.length > 0) {
|
||||
assembly {
|
||||
success := mload(add(data, 0x20))
|
||||
|
@ -23,10 +23,10 @@ contract ETHMixer is Mixer {
|
||||
) Mixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public {
|
||||
}
|
||||
|
||||
function _processWithdraw(address payable _receiver, uint256 _fee) internal {
|
||||
function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal {
|
||||
_receiver.transfer(mixDenomination - _fee);
|
||||
if (_fee > 0) {
|
||||
operator.transfer(_fee);
|
||||
_relayer.transfer(_fee);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ contract MerkleTreeWithHistory {
|
||||
|
||||
function _insert(uint256 leaf) internal {
|
||||
uint32 current_index = next_index;
|
||||
require(current_index != 2**(levels - 1), "Merkle tree is full");
|
||||
require(current_index != 2**(levels - 1), "Merkle tree is full. No more leafs can be added");
|
||||
next_index += 1;
|
||||
uint256 current_level_hash = leaf;
|
||||
uint256 left;
|
||||
@ -133,5 +133,3 @@ contract MerkleTreeWithHistory {
|
||||
return _zeros;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ pragma solidity ^0.5.8;
|
||||
import "./MerkleTreeWithHistory.sol";
|
||||
|
||||
contract IVerifier {
|
||||
function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public returns(bool);
|
||||
function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[5] memory input) public returns(bool);
|
||||
}
|
||||
|
||||
contract Mixer is MerkleTreeWithHistory {
|
||||
@ -29,7 +29,7 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
uint256 public mixDenomination;
|
||||
|
||||
event Deposit(uint256 indexed commitment, uint256 leafIndex, uint256 timestamp);
|
||||
event Withdraw(address to, uint256 nullifierHash, uint256 fee);
|
||||
event Withdraw(address to, uint256 nullifierHash, address indexed relayer, uint256 fee);
|
||||
|
||||
/**
|
||||
@dev The constructor
|
||||
@ -75,19 +75,20 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
- 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 {
|
||||
function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[5] memory input) public {
|
||||
uint256 root = input[0];
|
||||
uint256 nullifierHash = input[1];
|
||||
address payable receiver = address(input[2]);
|
||||
uint256 fee = input[3];
|
||||
address payable relayer = address(input[3]);
|
||||
uint256 fee = input[4];
|
||||
require(fee < mixDenomination, "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(a, b, c, input), "Invalid withdraw proof");
|
||||
nullifierHashes[nullifierHash] = true;
|
||||
_processWithdraw(receiver, fee);
|
||||
emit Withdraw(receiver, nullifierHash, fee);
|
||||
_processWithdraw(receiver, relayer, fee);
|
||||
emit Withdraw(receiver, nullifierHash, relayer, fee);
|
||||
}
|
||||
|
||||
function toggleDeposits() external {
|
||||
@ -105,6 +106,6 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
}
|
||||
|
||||
function _processDeposit() internal {}
|
||||
function _processWithdraw(address payable _receiver, uint256 _fee) internal {}
|
||||
function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal {}
|
||||
|
||||
}
|
||||
|
@ -127,6 +127,7 @@ contract('ERC20Mixer', accounts => {
|
||||
// public
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer,
|
||||
receiver,
|
||||
fee,
|
||||
|
||||
@ -159,14 +160,15 @@ contract('ERC20Mixer', accounts => {
|
||||
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)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore))
|
||||
balanceMixerAfter.should.be.eq.BN(toBN(balanceMixerBefore).sub(toBN(tokenDenomination)))
|
||||
balanceRelayerAfter.should.be.eq.BN(toBN(balanceRelayerBefore).add(feeBN))
|
||||
ethBalanceOperatorAfter.should.be.eq.BN(toBN(ethBalanceOperatorBefore))
|
||||
balanceRecieverAfter.should.be.eq.BN(toBN(balanceRecieverBefore).add(toBN(tokenDenomination).sub(feeBN)))
|
||||
ethBalanceRecieverAfter.should.be.eq.BN(toBN(ethBalanceRecieverBefore).add(toBN(value)))
|
||||
|
||||
logs[0].event.should.be.equal('Withdraw')
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
logs[0].args.relayer.should.be.eq.BN(relayer)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(true)
|
||||
@ -207,6 +209,7 @@ contract('ERC20Mixer', accounts => {
|
||||
// public
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
|
||||
@ -249,6 +252,7 @@ contract('ERC20Mixer', accounts => {
|
||||
|
||||
logs[0].event.should.be.equal('Withdraw')
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
logs[0].args.relayer.should.be.eq.BN(operator)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(true)
|
||||
@ -285,6 +289,7 @@ contract('ERC20Mixer', accounts => {
|
||||
// public
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
|
||||
@ -328,6 +333,7 @@ contract('ERC20Mixer', accounts => {
|
||||
|
||||
logs[0].event.should.be.equal('Withdraw')
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
logs[0].args.relayer.should.be.eq.BN(operator)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(true)
|
||||
|
@ -141,6 +141,7 @@ contract('ETHMixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
@ -196,6 +197,7 @@ contract('ETHMixer', accounts => {
|
||||
// public
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
|
||||
@ -235,6 +237,7 @@ contract('ETHMixer', accounts => {
|
||||
|
||||
logs[0].event.should.be.equal('Withdraw')
|
||||
logs[0].args.nullifierHash.should.be.eq.BN(toBN(input.nullifierHash.toString()))
|
||||
logs[0].args.relayer.should.be.eq.BN(operator)
|
||||
logs[0].args.fee.should.be.eq.BN(feeBN)
|
||||
isSpent = await mixer.isSpent(input.nullifierHash.toString(16).padStart(66, '0x00000'))
|
||||
isSpent.should.be.equal(true)
|
||||
@ -251,6 +254,7 @@ contract('ETHMixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
@ -275,6 +279,7 @@ contract('ETHMixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
@ -299,6 +304,7 @@ contract('ETHMixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee: oneEtherFee,
|
||||
secret: deposit.secret,
|
||||
@ -323,6 +329,7 @@ contract('ETHMixer', accounts => {
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
root,
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
@ -350,6 +357,7 @@ contract('ETHMixer', accounts => {
|
||||
root,
|
||||
nullifierHash: pedersenHash(deposit.nullifier.leInt2Buff(31)),
|
||||
nullifier: deposit.nullifier,
|
||||
relayer: operator,
|
||||
receiver,
|
||||
fee,
|
||||
secret: deposit.secret,
|
||||
|
@ -185,10 +185,19 @@ contract('MerkleTreeWithHistory', accounts => {
|
||||
}
|
||||
|
||||
let error = await merkleTreeWithHistory.insert(1337).should.be.rejected
|
||||
error.reason.should.be.equal('Merkle tree is full')
|
||||
error.reason.should.be.equal('Merkle tree is full. No more leafs can be added')
|
||||
|
||||
error = await merkleTreeWithHistory.insert(1).should.be.rejected
|
||||
error.reason.should.be.equal('Merkle tree is full')
|
||||
error.reason.should.be.equal('Merkle tree is full. No more leafs can be added')
|
||||
})
|
||||
|
||||
it.skip('mimc gas', async () => {
|
||||
levels = 6
|
||||
zeroValue = 1337
|
||||
merkleTreeWithHistory = await MerkleTreeWithHistory.new(levels, zeroValue)
|
||||
|
||||
const gas = await merkleTreeWithHistory.hashLeftRight.estimateGas(zeroValue, zeroValue)
|
||||
console.log('gas', gas - 21000)
|
||||
})
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user