Compare commits

..

3 Commits

2 changed files with 64 additions and 104 deletions

121
README.md

@ -1,6 +1,6 @@
# Tornado-CLI # Tornado-CLI
Command line tool to interact with [Tornado Cash](https://tornadocashcommunity.eth.link). Command line tool to interact with [Tornado Cash](https://tornado.ws).
### Warning! ### Warning!
Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medium.com/tornado-cash-governance-proposal-a55c5c7d0703) Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medium.com/tornado-cash-governance-proposal-a55c5c7d0703)
@ -19,12 +19,10 @@ You also need to install C++ build tools in order to do 'npm install', for more
If you have git installed on your system, clone the master branch. If you have git installed on your system, clone the master branch.
```bash ```bash
$ git clone https://development.tornadocash.community/tornadocash/tornado-cli $ git clone https://git.tornado.ws/tornadocash/tornado-cli
``` ```
Or, download the archive file from github Or, download the archive file from git: https://git.tornado.ws/tornadocash/tornado-cli/archive/master.zip
https://development.tornadocash.community/tornadocash/tornado-cli/archive/refs/heads/master.zip
After downloading or cloning the repository, you must install necessary libraries using the following command. After downloading or cloning the repository, you must install necessary libraries using the following command.
@ -38,23 +36,23 @@ If you want to use Tor connection to conceal ip address, install [Tor Browser](h
Note that you should reset your tor connection by restarting the browser every time when you deposit & withdraw otherwise you will have the same exit node used for connection. Note that you should reset your tor connection by restarting the browser every time when you deposit & withdraw otherwise you will have the same exit node used for connection.
### Goerli, Mainnet, Binance Smart Chain, Gnosis Chain, Polygon Network, Arbitrum, Avalanche ### Goerli, Mainnet, Binance Smart Chain, Gnosis Chain, Polygon Network, Arbitrum, Avalanche
1. Add `PRIVATE_KEY` to `.env` file 1. `node cli.js --help`
2. `node cli.js --help` 2. If you want to use secure, anonymous tor connection add `--tor <torPort>` behind the command.
3. If you want to use secure, anonymous tor connection add `--tor <torPort>` behind the command. 3. Add `PRIVATE_KEY` to `.env` file (optional, only if you want to use it for many operations) - open `.env.example` file, add private key after `PRIVATE_KEY=` and rename file to `.env`.
#### To deposit: #### To deposit:
```bash ```bash
$ node cli.js deposit <currency> <amount> --rpc <rpc url> --tor <torPort> $ node cli.js deposit <currency> <amount> --rpc <rpc url> --tor <torPort> --private-key <private key>
``` ```
Note that `--tor <torPort>` is optional. Note that `--tor <torPort>` is optional, and use `--private-key <private key>` only if you didn't add it to `.env` file.
For RPC nodes please refer to the list of public RPC nodes below. For RPC nodes please refer to the list of public RPC nodes below.
##### Example: ##### Example:
```bash ```bash
$ node cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9150 $ node cli.js deposit ETH 0.1 --rpc https://mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607 --tor 9150
Your note: tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 Your note: tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
Tornado ETH balance is 8.9 Tornado ETH balance is 8.9
@ -67,21 +65,19 @@ Sender account ETH balance is 1004873.361652048361352542
#### To withdraw: #### To withdraw:
```bash ```bash
$ node cli.js withdraw <note> <recipient> --rpc <rpc url> --relayer <relayer url> --tor <torPort> $ node cli.js withdraw <note> <recipient> --rpc <rpc url> --relayer <relayer url> --tor <torPort> --private-key <private key>
``` ```
Note that `--relayer <relayer url>`, `--tor <torPort>` is optional. Note that `--relayer <relayer url>`, `--tor <torPort>` is optional, and use `--private-key <private key>` only if you withdraw without relayer.
If you want to use Tornado Cash relayer for your first withdrawal to your new ethereum account, please refer to the list of relayers below. If you want to use Tornado Cash relayer for your first withdrawal to your new ethereum account, please refer to the list of relayers below.
If you don't need relayer while doing withdrawals, you must apply your withdrawal account's private key to `.env` file. If you don't need relayer while doing withdrawals, you must provide your withdrawal account's private key - either as parameter, or by adding it to `.env` file.
Copy the `PRIVATE_KEY=` line of `.env.example` to `.env`, and add your private key behind the `=`.
##### Example: ##### Example:
```bash ```bash
$ node cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9150 $ node cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607 --relayer https://goerli-relay.example.org --tor 9150
Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754 Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
Getting current state from tornado contract Getting current state from tornado contract
@ -141,42 +137,19 @@ Tornado contract balance is xxx.x ETH
Sender account balance is x.xxxxxxx ETH Sender account balance is x.xxxxxxx ETH
``` ```
#### To withdraw, you will need deposit note that matches with your deposit transaction. ### List of rpc & relayers for withdrawal
```bash
$ node cli.js withdraw <note> <recipient>
```
##### Example:
```bash
$ node cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9150
Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
Getting current state from tornado contract
Generating SNARK proof
Proof time: 9117.051ms
Sending withdraw transaction through relay
Transaction submitted through the relay. View transaction on etherscan https://goerli.etherscan.io/tx/0xcb21ae8cad723818c6bc7273e83e00c8393fcdbe74802ce5d562acad691a2a7b
Transaction mined in block 17036120
Done
```
### List of public rpc & relayers for withdrawal
Infura API key fetched from https://rpc.info (Same one with Metamask)
```json ```json
{ {
"netId1":{ "netId1":{
"rpcUrls":{ "rpcUrls":{
"publicRpc1":{ "publicRpc1":{
"name":"SecureRPC", "name":"1RPC",
"url":"https://api.securerpc.com/v1" "url":"https://1rpc.io/eth"
}, },
"publicRpc2":{ "Chainnodes":{
"name":"CloudFlare", "name": "Chainnodes",
"url":"https://cloudflare-eth.com" "url": "https://mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607"
} }
}, },
"relayers": { "relayers": {
@ -276,7 +249,7 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
"rpcUrls":{ "rpcUrls":{
"publicRpc1":{ "publicRpc1":{
"name":"BSC Public RPC 1", "name":"BSC Public RPC 1",
"url":"https://bsc-dataseed.binance.org" "url":"https://1rpc.io/bnb"
}, },
"publicRpc2":{ "publicRpc2":{
"name":"BSC Public RPC 2", "name":"BSC Public RPC 2",
@ -365,7 +338,7 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
"publicRpc":{ "publicRpc":{
"name":"Gnosis Chain RPC", "name":"Gnosis Chain RPC",
"url":"https://rpc.gnosischain.com" "url":"https://rpc.gnosischain.com"
}, }
}, },
"relayers":{ "relayers":{
"torndao.eth": { "torndao.eth": {
@ -378,20 +351,12 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
"netId137":{ "netId137":{
"rpcUrls":{ "rpcUrls":{
"publicRpc1":{ "publicRpc1":{
"name":"publicRpc1", "name":"1RPC",
"url":"https://rpc-mainnet.maticvigil.com" "url":"https://1rpc.io/matic"
}, },
"publicRpc2":{ "Chainnodes":{
"name":"publicRpc2", "name": "Chainnodes",
"url":"https://rpc-mainnet.matic.network" "url": "https://polygon-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607"
},
"publicRpc3":{
"name":"publicRpc3",
"url":"https://matic-mainnet.chainstacklabs.com"
},
"MyEtherWallet":{
"name":"MyEtherWallet",
"url":"https://nodes.mewapi.io/ws/matic"
} }
}, },
"relayers": { "relayers": {
@ -442,6 +407,10 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
"publicRpc1":{ "publicRpc1":{
"name":"Arbitrum Public RPC", "name":"Arbitrum Public RPC",
"url":"https://arb1.arbitrum.io/rpc" "url":"https://arb1.arbitrum.io/rpc"
},
"publicRpc2":{
"name": "ChainnodesRPC",
"url": "https://arbitrum-one.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607"
} }
}, },
"relayers":{ "relayers":{
@ -460,8 +429,8 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
"netId43114":{ "netId43114":{
"rpcUrls":{ "rpcUrls":{
"publicRpc":{ "publicRpc":{
"name":"Avalanche RPC", "name":"1RPC",
"url":"https://api.avax.network/ext/bc/C/rpc" "url":"https://1rpc.io/avax/c"
} }
}, },
"relayers":{ "relayers":{
@ -489,9 +458,13 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
}, },
"netId10":{ "netId10":{
"rpcUrls":{ "rpcUrls":{
"Optimism":{ "publicRpc1":{
"name":"Optimism Public RPC", "name":"1RPC",
"url":"https://mainnet.optimism.io" "url":"https://1rpc.io/op"
},
"Chainnodes":{
"name": "Chainnodes",
"url": "https://optimism-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607"
} }
}, },
"relayers":{ "relayers":{
@ -504,18 +477,10 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
}, },
"netId5":{ "netId5":{
"rpcUrls":{ "rpcUrls":{
"publicRpc1":{ "Chainnodes":{
"name":"Infura", "name":"Chainnodes RPC",
"url":"https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161" "url":"https://goerli.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607"
}, }
"Slockit":{
"name":"Slockit",
"url":"https://rpc.slock.it/goerli"
},
"Prylabs":{
"name":"Prylabs",
"url":"https://goerli.prylabs.net"
},
}, },
"relayers":{} "relayers":{}
} }

47
cli.js

@ -373,25 +373,12 @@ async function deposit({ currency, amount, commitmentNote }) {
* @param deposit Deposit object * @param deposit Deposit object
*/ */
async function generateMerkleProof(deposit, currency, amount) { async function generateMerkleProof(deposit, currency, amount) {
let leafIndex = -1;
// Get all deposit events from smart contract and assemble merkle tree from them // Get all deposit events from smart contract and assemble merkle tree from them
const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount }); const cachedEvents = await fetchEvents({ type: 'deposit', currency, amount });
const { tree, leaves, root } = computeDepositEventsTree(cachedEvents);
const leaves = cachedEvents // Validate that merkle tree is valid, deposit data is correct and note not spent.
.sort((a, b) => a.leafIndex - b.leafIndex) // Sort events in chronological order const leafIndex = leaves.findIndex((commitment) => toBN(deposit.commitmentHex).toString(10) === commitment);
.map((e) => {
const index = toBN(e.leafIndex).toNumber();
if (toBN(e.commitment).eq(toBN(deposit.commitmentHex))) {
leafIndex = index;
}
return toBN(e.commitment).toString(10);
});
const tree = new merkleTree(MERKLE_TREE_HEIGHT, leaves);
// Validate that our data is correct
const root = tree.root();
let isValidRoot, isSpent; let isValidRoot, isSpent;
if (!isTestRPC && !multiCall) { if (!isTestRPC && !multiCall) {
const callContract = await useMultiCall([ const callContract = await useMultiCall([
@ -1381,7 +1368,7 @@ async function promptConfirmation() {
/** /**
* Init web3, contracts, and snark * Init web3, contracts, and snark
*/ */
async function init({ rpc, noteNetId, currency = 'dai', amount = '100', balanceCheck, localMode }) { async function init({ rpc, noteNetId, currency = 'dai', amount = '100', balanceCheck, localMode, privateKey }) {
let contractJson, instanceJson, erc20ContractJson, erc20tornadoJson, tornadoAddress, tokenAddress; let contractJson, instanceJson, erc20ContractJson, erc20tornadoJson, tornadoAddress, tokenAddress;
let ipOptions = {}; let ipOptions = {};
@ -1441,13 +1428,13 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', balanceC
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20; MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20;
ETH_AMOUNT = process.env.ETH_AMOUNT; ETH_AMOUNT = process.env.ETH_AMOUNT;
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT; TOKEN_AMOUNT = process.env.TOKEN_AMOUNT;
const privKey = process.env.PRIVATE_KEY; const privKey = privateKey || process.env.PRIVATE_KEY;
if (privKey) { if (privKey) {
if (privKey.includes('0x')) { if (privKey.startsWith('0x')) {
PRIVATE_KEY = process.env.PRIVATE_KEY.substring(2); PRIVATE_KEY = privKey.substring(2);
} else { } else {
PRIVATE_KEY = process.env.PRIVATE_KEY; PRIVATE_KEY = privKey;
} }
} }
if (PRIVATE_KEY) { if (PRIVATE_KEY) {
@ -1515,6 +1502,7 @@ async function main() {
.option('-r, --rpc <URL>', 'The RPC that CLI should interact with', 'http://localhost:8545') .option('-r, --rpc <URL>', 'The RPC that CLI should interact with', 'http://localhost:8545')
.option('-R, --relayer <URL>', 'Withdraw via relayer') .option('-R, --relayer <URL>', 'Withdraw via relayer')
.option('-T, --tor <PORT>', 'Optional tor port') .option('-T, --tor <PORT>', 'Optional tor port')
.option('-p, --private-key <KEY>', "Wallet private key - If you didn't add it to .env file and it is needed for operation")
.option('-S --gas_speed <SPEED>', 'Gas speed preference [ instant, fast, standard, low ]') .option('-S --gas_speed <SPEED>', 'Gas speed preference [ instant, fast, standard, low ]')
.option('-N --noconfirmation', 'No confirmation mode - Does not query confirmation ') .option('-N --noconfirmation', 'No confirmation mode - Does not query confirmation ')
.option('-L, --local-rpc', 'Local node mode - Does not submit signed transaction to the node') .option('-L, --local-rpc', 'Local node mode - Does not submit signed transaction to the node')
@ -1535,7 +1523,7 @@ async function main() {
statePreferences(program); statePreferences(program);
const { currency, amount, netId, commitmentNote } = parseInvoice(invoice); const { currency, amount, netId, commitmentNote } = parseInvoice(invoice);
await init({ rpc: program.rpc, currency, amount, localMode: program.local }); await init({ rpc: program.rpc, currency, amount, localMode: program.local, privateKey: program.privateKey });
console.log('Creating', currency.toUpperCase(), amount, 'deposit for', netName, 'Tornado Cash Instance'); console.log('Creating', currency.toUpperCase(), amount, 'deposit for', netName, 'Tornado Cash Instance');
await deposit({ currency, amount, commitmentNote }); await deposit({ currency, amount, commitmentNote });
}); });
@ -1549,7 +1537,7 @@ async function main() {
statePreferences(program); statePreferences(program);
await init({ rpc: program.rpc, currency, amount, localMode: program.local }); await init({ rpc: program.rpc, currency, amount, localMode: program.local, privateKey: program.privateKey });
await deposit({ currency, amount }); await deposit({ currency, amount });
}); });
program program
@ -1562,7 +1550,14 @@ async function main() {
const { currency, amount, netId, deposit } = parseNote(noteString); const { currency, amount, netId, deposit } = parseNote(noteString);
await init({ rpc: program.rpc, noteNetId: netId, currency, amount, localMode: program.local }); await init({
rpc: program.rpc,
noteNetId: netId,
currency,
amount,
localMode: program.local,
privateKey: program.privateKey
});
await withdraw({ await withdraw({
deposit, deposit,
currency, currency,
@ -1594,7 +1589,7 @@ async function main() {
.action(async (address, amount, tokenAddress) => { .action(async (address, amount, tokenAddress) => {
statePreferences(program); statePreferences(program);
await init({ rpc: program.rpc, balanceCheck: true, localMode: program.local }); await init({ rpc: program.rpc, balanceCheck: true, localMode: program.local, privateKey: program.privateKey });
await send({ address, amount, tokenAddress }); await send({ address, amount, tokenAddress });
}); });
program program
@ -1706,7 +1701,7 @@ async function main() {
console.log('Start performing ETH deposit-withdraw test'); console.log('Start performing ETH deposit-withdraw test');
let currency = 'eth'; let currency = 'eth';
let amount = '0.1'; let amount = '0.1';
await init({ rpc: program.rpc, currency, amount }); await init({ rpc: program.rpc, currency, amount, privateKey: program.privateKey });
let noteString = await deposit({ currency, amount }); let noteString = await deposit({ currency, amount });
let parsedNote = parseNote(noteString); let parsedNote = parseNote(noteString);
await withdraw({ await withdraw({