diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f90d9ee --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# FROM node 14.21.3-bullseye-slim +FROM node@sha256:0f5b374fae506741ff14db84daff2937ae788e88fb48a6c66d15de5ee808ccd3 + +RUN apt update && apt install --yes --no-install-recommends wget git apt-transport-https ca-certificates && rm -rf /var/lib/apt/lists/* + +WORKDIR /home/root/tornado-cli + +ENV GIT_REPOSITORY=https://git.tornado.ws/tornadosto/tornado-cli.git +ENV GIT_COMMIT_HASH=1ae2aec71d3cfb28911ce4c60bdd35650e93e5e4 + +RUN git init && \ + git remote add origin $GIT_REPOSITORY && \ + git fetch --depth 1 origin $GIT_COMMIT_HASH && \ + git checkout $GIT_COMMIT_HASH + +RUN npm ci + +RUN npm install -g pkg@5.8.1 + +RUN node scripts/createDeterministicExecutable.js + +RUN printf '#!/bin/sh\ncp /home/root/tornado-cli/tornado-cli.exe /output/' > /copy_out.sh && chmod +x /copy_out.sh + +CMD ["/bin/bash"] \ No newline at end of file diff --git a/README.md b/README.md index 70cbb8d..6d87e6c 100644 --- a/README.md +++ b/README.md @@ -163,3 +163,21 @@ View transaction on block explorer https://goerli.etherscan.io/tx/0x6ded443caed8 Tornado contract balance is xxx.x ETH Sender account balance is x.xxxxxxx ETH ``` + +#### To verify: + +```bash +$ docker build -t tornado-cli:latest . +``` +wait for docker to build + +```bash +$ docker run --rm -v %cd%/output:/output tornado-cli:latest /copy_out.sh +``` +copy exe to current folder in windows + +```bash +CertUtil -hashfile output/tornado-cli.exe SHA256 +CertUtil -hashfile tornado-cli.exe SHA256 +``` +compare with the exe in git diff --git a/cli.js b/cli.js index 735b153..5b91e40 100755 --- a/cli.js +++ b/cli.js @@ -74,7 +74,8 @@ const relayerSubdomains = Object.values(config.deployments).map(({ ensSubdomainK */ /** @type {ProgramGlobals} */ -const globals = { +const globals = +{ privateKey: undefined, web3Instance: undefined, relayerWeb3Instance: undefined, @@ -288,7 +289,8 @@ async function generateTransaction(to, encodedData, value = 0, txType = 'other') /** * Create deposit object from secret and nullifier */ -function createDeposit({ nullifier, secret }) { +function createDeposit({ nullifier, secret }) +{ let deposit = { nullifier, secret }; deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)]); deposit.commitment = pedersenHash(deposit.preimage); @@ -328,7 +330,8 @@ async function backupInvoice({ currency, amount, netId, commitmentNote, invoiceS * @param currency Сurrency * @param amount Deposit amount */ -async function createInvoice({ currency, amount, chainId }) { +async function createInvoice({ currency, amount, chainId }) +{ const deposit = createDeposit({ nullifier: rbigint(31), secret: rbigint(31) @@ -422,7 +425,8 @@ async function deposit({ currency, amount, commitmentNote }) { * @param {number} amount Tornado instance amount, like 0.1 (ETH or BNB) or 10 * @return {Promise} Calculated valid merkle tree (proof) */ -async function generateMerkleProof(deposit, currency, amount) { +async function generateMerkleProof(deposit, currency, amount) +{ const { web3Instance, multiCallAddress, tornadoInstanceContract } = globals; // Get all deposit events from smart contract and assemble merkle tree from them @@ -469,13 +473,16 @@ async function generateMerkleProof(deposit, currency, amount) { * @param {MerkleProof} [args.merkleProof] Valid merkle tree proof * @returns {Promise} Proof data */ -async function generateProof({ deposit, currency, amount, recipient, relayerAddress = 0, fee = 0, refund = 0, merkleProof }) { +async function generateProof({ deposit, currency, amount, recipient, relayerAddress = 0, fee = 0, refund = 0, merkleProof }) +{ // Compute merkle proof of our commitment - if (merkleProof === undefined) merkleProof = await generateMerkleProof(deposit, currency, amount); + if (merkleProof === undefined) + merkleProof = await generateMerkleProof(deposit, currency, amount); const { root, pathElements, pathIndices } = merkleProof; // Prepare circuit input - const input = { + const input = + { // Public snark inputs root: root, nullifierHash: deposit.nullifierHash, @@ -518,65 +525,86 @@ async function generateProof({ deposit, currency, amount, recipient, relayerAddr * @param noteString Note to withdraw * @param recipient Recipient address */ -async function withdraw({ deposit, currency, amount, recipient, relayerURL, refund, privateKey }) { +async function withdraw({ deposit, currency, amount, recipient, relayerURL, refund, privateKey }) +{ const { web3Instance, signerAddress, tornadoProxyAddress, requestOptions, feeOracle, tornadoInstanceAddress, tornadoProxyContract, netSymbol, netId, shouldPromptConfirmation } = globals; - if (currency === netSymbol.toLowerCase() && refund && refund !== '0') { + if (currency === netSymbol.toLowerCase() && refund && refund !== '0') + { throw new Error('The ETH purchase is supposed to be 0 for ETH withdrawals'); } - if (!isNaN(Number(refund))) refund = toWei(refund, 'ether'); - else refund = toBN(await feeOracle.fetchRefundInETH(currency.toLowerCase())); + if (!isNaN(Number(refund))) + refund = toWei(refund, 'ether'); + else + refund = toBN(await feeOracle.fetchRefundInETH(currency.toLowerCase())); - if (!web3Utils.isAddress(recipient)) { + if (!web3Utils.isAddress(recipient)) + { throw new Error('Recipient address is not valid'); } const depositInfo = await loadDepositData({ amount, currency, deposit }); const allDeposits = loadCachedEvents({ type: "deposit", currency, amount }); - if ((depositInfo.leafIndex > allDeposits[allDeposits.length - 1].leafIndex - 10) && allDeposits.length > 10){ + if ((depositInfo.leafIndex > allDeposits[allDeposits.length - 1].leafIndex - 10) + && allDeposits.length > 10) + { console.log("\nWARNING: you're trying to withdraw your deposit too early, there are not enough subsequent deposits to ensure good anonymity level. Read: https://docs.tornado.ws/general/guides/opsec.html"); - if (shouldPromptConfirmation) await promptConfirmation("Continue withdrawal with risks to anonymity? [Y/n]: ") + if (shouldPromptConfirmation) + await promptConfirmation("Continue withdrawal with risks to anonymity? [Y/n]: ") } const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit }); - if(withdrawInfo) { + if(withdrawInfo) + { console.error("\nError: note has already been withdrawn. Use `compliance` command to check deposit and withdrawal info.\n"); process.exit(1); } - if (privateKey || globals.privateKey) { + if (privateKey || globals.privateKey) + { // using private key // check if the address of recepient matches with the account of provided private key from environment to prevent accidental use of deposit address for withdrawal transaction. - assert( + assert + ( recipient.toLowerCase() == signerAddress.toLowerCase(), 'Withdrawal recepient mismatches with the account of provided private key from environment file' ); const checkBalance = await web3Instance.getBalance(signerAddress); - assert(checkBalance !== 0, 'You have 0 balance, make sure to fund account by withdrawing from tornado using relayer first'); + assert + ( + checkBalance !== 0, + 'You have 0 balance, make sure to fund account by withdrawing from tornado using relayer first' + ); const { proof, args } = await generateProof({ deposit, currency, amount, recipient, refund }); console.log('Submitting withdraw transaction'); - await generateTransaction( + await generateTransaction + ( tornadoProxyAddress, tornadoProxyContract.methods.withdraw(tornadoInstanceAddress, proof, ...args).encodeABI(), toBN(args[5]), 'user_withdrawal' ); } - else { + else + { let relayerInfo; - if (relayerURL) { - try { + if (relayerURL) + { + try + { relayerURL = new URL(relayerURL).origin; res = await axios.get(relayerURL + '/status', requestOptions); relayerInfo = res.data; - } catch (err) { + } catch (err) + { console.error(err); throw new Error('Cannot get relayer status'); } } - else { + else + { const availableRelayers = await getRelayers(netId); if(availableRelayers.length === 0) throw new Error("Cannot automatically pick a relayer to withdraw your note. Provide relayer manually with `--relayer` cmd option or use private key withdrawal") relayerInfo = pickWeightedRandomRelayer(availableRelayers); @@ -667,9 +695,12 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu } } - if (currency === netSymbol.toLowerCase()) { + if (currency === netSymbol.toLowerCase()) + { await printETHBalance({ address: recipient, name: 'Recipient' }); - } else { + } + else + { await printERC20Balance({ address: recipient, name: 'Recipient' }); } console.log('Done withdrawal from Tornado Cash'); @@ -870,6 +901,10 @@ function toDecimals(value, decimals, fixed) { // List fetched from https://github.com/ethereum-lists/chains/blob/master/_data/chains function getExplorerLink() { switch (globals.netId) { + case 61: + return 'etc.blockscout.com'; + case 11155111: + return 'sepolia.etherscan.io'; case 56: return 'bscscan.com'; case 100: @@ -894,6 +929,10 @@ function getExplorerLink() { // List fetched from https://github.com/trustwallet/assets/tree/master/blockchains function getCurrentNetworkName() { switch (globals.netId) { + case 61: + return 'EthereumClassic'; + case 11155111: + return 'Sepolia'; case 56: return 'BinanceSmartChain'; case 100: @@ -920,6 +959,8 @@ function getCurrentNetworkName() { */ function getCurrentNetworkSymbol(chainId) { switch (Number(chainId)) { + case 61: + return 'ETC'; case 56: return 'BNB'; case 100: @@ -1399,10 +1440,12 @@ async function fetchEvents({ type, currency, amount }) { * Parses Tornado Cash note * @param {string} noteString the note */ -function parseNote(noteString) { +function parseNote(noteString) +{ const noteRegex = /tornado-(?\w+)-(?[\d.]+)-(?\d+)-0x(?[0-9a-fA-F]{124})/g; const match = noteRegex.exec(noteString); - if (!match) { + if (!match) + { throw new Error('The note has invalid format'); } @@ -1563,7 +1606,7 @@ async function initNetwork({rpc, chainId, privateKey, torPort, onlyRpc, eventTyp } globals.web3Instance = await createWeb3Instance(rpc) - globals.netId = await globals.web3Instance.net.getId(); + globals.netId = await globals.web3Instance.getChainId() globals.netName = getCurrentNetworkName(); globals.netSymbol = getCurrentNetworkSymbol(globals.netId); @@ -1633,8 +1676,9 @@ async function init({ rpc, chainId, currency = 'dai', amount = '100', privateKey initPreferences({nonconfirmation, localMode}); await initNetwork({rpc, chainId, privateKey, torPort, onlyRpc, eventType, relayer}); - + const { netId, web3Instance } = globals; + // console.log(netId, chainId); if (chainId && Number(chainId) !== netId) { throw new Error('This note is for a different network. Specify the --rpc option explicitly'); } @@ -1870,4 +1914,4 @@ async function main() { } } -main(); +main(); \ No newline at end of file diff --git a/config.js b/config.js index ef96ef4..67cdb36 100755 --- a/config.js +++ b/config.js @@ -114,9 +114,15 @@ module.exports = { relayerAggregator: '0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49', proxy: '0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b', multicall: '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441', - subgraphs: ['https://gateway.thegraph.com/api/6a217817dd87d33db10beed79b044a91/subgraphs/id/Ec6fVMDVqXTDQZ3c4jxcyV3zBXqkdgMWfhdtCgtqn7Sh', 'https://gateway.thegraph.com/api/8b164501e1862078eff5fb9dda136c6c/subgraphs/id/Ec6fVMDVqXTDQZ3c4jxcyV3zBXqkdgMWfhdtCgtqn7Sh', 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/mainnet-tornado-subgraph'], + subgraphs: ['https://gateway.thegraph.com/api/6a217817dd87d33db10beed79b044a91/subgraphs/id/Ec6fVMDVqXTDQZ3c4jxcyV3zBXqkdgMWfhdtCgtqn7Sh', + 'https://gateway.thegraph.com/api/8b164501e1862078eff5fb9dda136c6c/subgraphs/id/Ec6fVMDVqXTDQZ3c4jxcyV3zBXqkdgMWfhdtCgtqn7Sh', + 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/mainnet-tornado-subgraph'], relayerSubgraphs: ['https://gateway.thegraph.com/api/6a217817dd87d33db10beed79b044a91/subgraphs/id/DgKwfAbLfynpiq7fDJy59LDnVnia4Y5nYeRDBYi9qezc', 'https://gateway.thegraph.com/api/8b164501e1862078eff5fb9dda136c6c/subgraphs/id/DgKwfAbLfynpiq7fDJy59LDnVnia4Y5nYeRDBYi9qezc', 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/tornado-relayer-registry'], - defaultRpcs: ['https://ethereum.blockpi.network/v1/rpc/public', 'https://eth.drpc.org', 'https://ethereum-rpc.publicnode.com', 'https://mainnet.chainnodes.org/3ae3d849-a613-4917-a56e-080f181aa4da', 'https://tornadocash-rpc.com'] + defaultRpcs: ['https://ethereum.blockpi.network/v1/rpc/public', + 'https://eth.drpc.org', + 'https://ethereum-rpc.publicnode.com', + 'https://mainnet.chainnodes.org/3ae3d849-a613-4917-a56e-080f181aa4da', + 'https://tornadocash-rpc.com'] }, netId56: { tokens: { @@ -143,7 +149,12 @@ module.exports = { proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17', multicall: '0x41263cBA59EB80dC200F3E2544eda4ed6A90E76C', subgraphs: ['https://gateway.thegraph.com/api/6a217817dd87d33db10beed79b044a91/subgraphs/id/CiwGzefDBZCavXRPnwarnnF8xDDoLw4boBuySomJWYnV', 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/bsc-tornado-subgraph', 'https://gateway.thegraph.com/api/8b164501e1862078eff5fb9dda136c6c/subgraphs/id/CiwGzefDBZCavXRPnwarnnF8xDDoLw4boBuySomJWYnV'], - defaultRpcs: ['https://bsc-rpc.publicnode.com', 'https://endpoints.omniatech.io/v1/bsc/mainnet/public', 'https://bsc-mainnet.chainnodes.org/3ae3d849-a613-4917-a56e-080f181aa4da', 'https://bsc-dataseed1.ninicoin.io', 'https://bsc.drpc.org', 'https://bsc-mainnet.public.blastapi.io'] + defaultRpcs: ['https://bsc-rpc.publicnode.com', + 'https://endpoints.omniatech.io/v1/bsc/mainnet/public', + 'https://bsc-mainnet.chainnodes.org/3ae3d849-a613-4917-a56e-080f181aa4da', + 'https://bsc-dataseed1.ninicoin.io', + 'https://bsc.drpc.org', + 'https://bsc-mainnet.public.blastapi.io'] }, netId100: { tokens: { @@ -277,6 +288,80 @@ module.exports = { multicall: '0x142E2FEaC30d7fc3b61f9EE85FCCad8e560154cc', subgraphs: ['https://gateway.thegraph.com/api/6a217817dd87d33db10beed79b044a91/subgraphs/id/GvkbnEVhLD6KArXpEzLFtSKRmspBW29ApKFqR5FjuP2P', 'https://gateway.thegraph.com/api/8b164501e1862078eff5fb9dda136c6c/subgraphs/id/GvkbnEVhLD6KArXpEzLFtSKRmspBW29ApKFqR5FjuP2P', 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/optimism-tornado-subgraph'], defaultRpcs: ['https://optimism.blockpi.network/v1/rpc/public', 'https://optimism-mainnet.chainnodes.org/3ae3d849-a613-4917-a56e-080f181aa4da', 'https://endpoints.omniatech.io/v1/op/mainnet/public', 'https://optimism-mainnet.public.blastapi.io', 'https://optimism.drpc.org'] + }, + netId11155111: { + tokens: + { + eth: + { + instanceAddress: + { + 0.1: '0x8C4A04d872a6C1BE37964A21ba3a138525dFF50b', + 1: '0x8cc930096B4Df705A007c4A039BDFA1320Ed2508', + 10: '0x8D10d506D29Fc62ABb8A290B99F66dB27Fc43585', + }, + deployedBlockNumber: + { + 0.1: 5594400, + 1: 5594401, + 10: 5594402, + }, + miningEnabled: false, + symbol: 'ETH', + decimals: 18 + } + }, + ensSubdomainKey: 'sepolia-tornado', + firstDeploymentTransaction: '0x7c7260a119bd0682b785da8860def277877ffaa50c2068ee78d6cb51f50bdc1f', + proxy: '0x1572AFE6949fdF51Cb3E0856216670ae9Ee160Ee', + multicall: '0x53c43764255c17bd724f74c4ef150724ac50a3ed', + subgraphs: ['https://gateway.thegraph.com/api/6a217817dd87d33db10beed79b044a91/subgraphs/id/8kJGz92AYUm72wfyUoze1as3E11ynDSTZM8emiRWrRPy', + 'https://gateway.thegraph.com/api/8b164501e1862078eff5fb9dda136c6c/subgraphs/id/8kJGz92AYUm72wfyUoze1as3E11ynDSTZM8emiRWrRPy', + 'https://tornadocash-rpc.com/subgraphs/name/tornadocash/sepolia-tornado-subgraph' + ], + defaultRpcs: ['https://ethereum-sepolia-rpc.publicnode.com', + 'https://sepolia.chainnodes.org/61b7de01-6cc4-40dc-a6c2-b6e4a61bb042', + 'https://sepolia.drpc.org', + 'https://eth-sepolia.g.alchemy.com/v2/demo', + 'https://eth-sepolia.public.blastapi.io', + 'https://eth-sepolia.api.onfinality.io/public'] + + }, + netId61: + { + tokens: + { + etc: + { + instanceAddress: + { + 1: '0x2f56d5aFC058B8734350B162EFEe75ee48f034e0', + 10: '0x59fCB629A23e8eD0a60A0188771E221042260118', + 100: '0x784B3a7a7981B959bd8d9D9e73c2013BE819Fbf2', + }, + deployedBlockNumber: + { + 1: 22385618, + 10: 22385618, + 100: 22385618, + }, + miningEnabled: false, + symbol: 'ETC', + decimals: 18 + } + }, + ensSubdomainKey: 'etc-tornado', + firstDeploymentTransaction: '0x4d9232046d3503138525bb3b921e131153d02b436f9f81426f52929e50ab359e', + proxy: '0xac97AB4fBd872ea762974CbBB0Ee72351afe16F3', + multicall: '0xA52EE88C0F24EF8b96C3989cAb42cfC6008041A8', + subgraphs: ['https://graph.torndao.com/subgraphs/name/tornadocash/etc-tornado-subgraph'], + defaultRpcs: ['https://etc.etcdesktop.com', + 'https://etc.rivet.link', + 'https://etc.mytokenpocket.vip', + 'https://0xrpc.io/etc', + 'https://geth-at.etc-network.info', + 'https://besu-at.etc-network.info'], + } } }; diff --git a/tornado-cli.exe b/tornado-cli.exe index 5533b23..99d2e2b 100644 Binary files a/tornado-cli.exe and b/tornado-cli.exe differ