From e9d78d3a7f9878fd9428937e763ea26c97998246 Mon Sep 17 00:00:00 2001 From: tornadocontrib Date: Mon, 22 Jul 2024 13:02:53 -0700 Subject: [PATCH] Initial commit --- .eslintrc.js | 27 ++ .gitignore | 5 + README.md | 24 ++ abi/ERC20.abi.json | 228 ++++++++++++++++ abi/TornadoProxy.abi.json | 237 +++++++++++++++++ networkConfig.js | 533 ++++++++++++++++++++++++++++++++++++++ package.json | 33 +++ tornadoOffline.js | 229 ++++++++++++++++ 8 files changed, 1316 insertions(+) create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 README.md create mode 100644 abi/ERC20.abi.json create mode 100644 abi/TornadoProxy.abi.json create mode 100644 networkConfig.js create mode 100644 package.json create mode 100644 tornadoOffline.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..ac40f10 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,27 @@ +module.exports = { + 'env': { + 'browser': false, + 'node': true, + 'commonjs': true, + 'es2022': true + }, + 'extends': 'eslint:recommended', + 'rules': { + 'indent': [ + 'error', + 2 + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'always' + ], + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2052dca --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +package-lock.json +yarn.lock +backup* +*.zip \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c03a41 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Offline Tornado Cash Note Generation Tool + +This tool allows you to create Tornado Cash deposit transaction data offline (So your note data is never exposed to online!) + +### How to use? + +```bash +# This will install all the necessary libraries +yarn + +node ./tornadoNote.js + +node ./tornadoNote.js --help + +node ./tornadoNote.js 1 eth 0.1 + +node ./tornadoNote.js 1 dai 10000 + +node ./tornadoNote.js 56 bnb 100 +``` + +Will also create backup similar with how Tornado UI or tornado-cli generates it + +Use Transaction Data to submit ethereum transaction on https://myetherwallet.com, https://mycrypto.com, or https://remix.ethereum.org \ No newline at end of file diff --git a/abi/ERC20.abi.json b/abi/ERC20.abi.json new file mode 100644 index 0000000..0e474b5 --- /dev/null +++ b/abi/ERC20.abi.json @@ -0,0 +1,228 @@ +[ + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "_totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/TornadoProxy.abi.json b/abi/TornadoProxy.abi.json new file mode 100644 index 0000000..8e6a9a9 --- /dev/null +++ b/abi/TornadoProxy.abi.json @@ -0,0 +1,237 @@ +[ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_tornadoTrees", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_governance", + "type": "bytes32" + }, + { + "internalType": "bytes32[]", + "name": "_instances", + "type": "bytes32[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "encryptedNote", + "type": "bytes" + } + ], + "name": "EncryptedNote", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "domains", + "type": "bytes32[]" + } + ], + "name": "bulkResolve", + "outputs": [ + { + "internalType": "address[]", + "name": "result", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITornadoInstance", + "name": "", + "type": "address" + } + ], + "name": "instances", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "resolve", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tornadoTrees", + "outputs": [ + { + "internalType": "contract ITornadoTrees", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITornadoInstance", + "name": "_tornado", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_commitment", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_encryptedNote", + "type": "bytes" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITornadoInstance", + "name": "_instance", + "type": "address" + }, + { + "internalType": "bool", + "name": "_update", + "type": "bool" + } + ], + "name": "updateInstance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITornadoInstance", + "name": "_tornado", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_proof", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "_root", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_nullifierHash", + "type": "bytes32" + }, + { + "internalType": "address payable", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "address payable", + "name": "_relayer", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_refund", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_token", + "type": "address" + }, + { + "internalType": "address payable", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_balance", + "type": "uint256" + } + ], + "name": "rescueTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/networkConfig.js b/networkConfig.js new file mode 100644 index 0000000..a69b190 --- /dev/null +++ b/networkConfig.js @@ -0,0 +1,533 @@ +const blockSyncInterval = 10000; +const enabledChains = ['1', '10', '56', '100', '137', '42161', '43114', '11155111']; +const networkConfig = { + netId1: { + rpcCallRetryAttempt: 15, + gasPrices: { + instant: 80, + fast: 50, + standard: 25, + low: 8 + }, + nativeCurrency: 'eth', + currencyName: 'ETH', + explorerUrl: { + tx: 'https://etherscan.io/tx/', + address: 'https://etherscan.io/address/', + block: 'https://etherscan.io/block/' + }, + merkleTreeHeight: 20, + emptyElement: '21663839004416932945382355908790599225266501822907911457504978515578255421292', + networkName: 'Ethereum Mainnet', + deployedBlock: 9116966, + rpcUrls: { + tornadoRPC: { + name: 'Tornado RPC', + url: 'https://tornadocash-rpc.com/mainnet' + }, + chainnodes: { + name: 'Chainnodes RPC', + url: 'https://mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607' + }, + mevblockerRPC: { + name: 'MevblockerRPC', + url: 'https://rpc.mevblocker.io' + }, + oneRPC: { + name: '1RPC', + url: 'https://1rpc.io/eth' + } + }, + multicall: '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', + routerContract: '0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b', + registryContract: '0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2', + echoContractAccount: '0x9B27DD5Bb15d42DC224FCD0B7caEbBe16161Df42', + aggregatorContract: '0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49', + tokens: { + eth: { + instanceAddress: { + '0.1': '0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc', + '1': '0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936', + '10': '0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF', + '100': '0xA160cdAB225685dA1d56aa342Ad8841c3b53f291' + }, + symbol: 'ETH', + decimals: 18 + }, + dai: { + instanceAddress: { + '100': '0xD4B88Df4D29F5CedD6857912842cff3b20C8Cfa3', + '1000': '0xFD8610d20aA15b7B2E3Be39B396a1bC3516c7144', + '10000': '0x07687e702b410Fa43f4cB4Af7FA097918ffD2730', + '100000': '0x23773E65ed146A459791799d01336DB287f25334' + }, + tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + symbol: 'DAI', + decimals: 18, + gasLimit: '55000' + }, + cdai: { + instanceAddress: { + '5000': '0x22aaA7720ddd5388A3c0A3333430953C68f1849b', + '50000': '0x03893a7c7463AE47D46bc7f091665f1893656003', + '500000': '0x2717c5e28cf931547B621a5dddb772Ab6A35B701', + '5000000': '0xD21be7248e0197Ee08E0c20D4a96DEBdaC3D20Af' + }, + tokenAddress: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', + symbol: 'cDAI', + decimals: 8, + gasLimit: '425000' + }, + /** + * Instances frozen due to sanctions + usdc: { + instanceAddress: { + '100': '0xd96f2B1c14Db8458374d9Aca76E26c3D18364307', + '1000': '0x4736dCf1b7A3d580672CcE6E7c65cd5cc9cFBa9D' + }, + tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + symbol: 'USDC', + decimals: 6, + gasLimit: '80000' + }, + usdt: { + instanceAddress: { + '100': '0x169AD27A470D064DEDE56a2D3ff727986b15D52B', + '1000': '0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f' + }, + tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + symbol: 'USDT', + decimals: 6, + gasLimit: '100000' + }, + **/ + wbtc: { + instanceAddress: { + '0.1': '0x178169B423a011fff22B9e3F3abeA13414dDD0F1', + '1': '0x610B717796ad172B316836AC95a2ffad065CeaB4', + '10': '0xbB93e510BbCD0B7beb5A853875f9eC60275CF498' + }, + tokenAddress: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + symbol: 'WBTC', + decimals: 8, + gasLimit: '85000' + } + }, + ensSubdomainKey: 'mainnet-tornado', + pollInterval: 15, + constants: { + GOVERNANCE_BLOCK: 11474695, + NOTE_ACCOUNT_BLOCK: 11842486, + ENCRYPTED_NOTES_BLOCK: 14248730, + MINING_BLOCK_TIME: 15 + }, + 'torn.contract.tornadocash.eth': '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C', + 'governance.contract.tornadocash.eth': '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce', + 'tornado-router.contract.tornadocash.eth': '0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b', + 'staking-rewards.contract.tornadocash.eth': '0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29' + }, + netId56: { + rpcCallRetryAttempt: 15, + gasPrices: { + instant: 5, + fast: 5, + standard: 5, + low: 5 + }, + nativeCurrency: 'bnb', + currencyName: 'BNB', + explorerUrl: { + tx: 'https://bscscan.com/tx/', + address: 'https://bscscan.com/address/', + block: 'https://bscscan.com/block/' + }, + merkleTreeHeight: 20, + emptyElement: '21663839004416932945382355908790599225266501822907911457504978515578255421292', + networkName: 'Binance Smart Chain', + deployedBlock: 8158799, + multicall: '0x41263cba59eb80dc200f3e2544eda4ed6a90e76c', + echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + rpcUrls: { + tornadoRPC: { + name: 'Tornado RPC', + url: 'https://tornadocash-rpc.com/bsc' + }, + chainnodes: { + name: 'Chainnodes RPC', + url: 'https://bsc-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607' + }, + oneRPC: { + name: '1RPC', + url: 'https://1rpc.io/bnb' + } + }, + tokens: { + bnb: { + instanceAddress: { + '0.1': '0x84443CFd09A48AF6eF360C6976C5392aC5023a1F', + '1': '0xd47438C816c9E7f2E2888E060936a499Af9582b3', + '10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a', + '100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD' + }, + symbol: 'BNB', + decimals: 18 + } + }, + ensSubdomainKey: 'bsc-tornado', + pollInterval: 10, + constants: { + NOTE_ACCOUNT_BLOCK: 8159269, + ENCRYPTED_NOTES_BLOCK: 8159269 + }, + 'tornado-proxy-light.contract.tornadocash.eth': '0x0D5550d52428E7e3175bfc9550207e4ad3859b17' + }, + netId137: { + rpcCallRetryAttempt: 15, + gasPrices: { + instant: 100, + fast: 75, + standard: 50, + low: 30 + }, + nativeCurrency: 'matic', + currencyName: 'MATIC', + explorerUrl: { + tx: 'https://polygonscan.com/tx/', + address: 'https://polygonscan.com/address/', + block: 'https://polygonscan.com/block/' + }, + merkleTreeHeight: 20, + emptyElement: '21663839004416932945382355908790599225266501822907911457504978515578255421292', + networkName: 'Polygon (Matic) Network', + deployedBlock: 16257962, + multicall: '0x11ce4B23bD875D7F5C6a31084f55fDe1e9A87507', + echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + rpcUrls: { + chainnodes: { + name: 'Chainnodes RPC', + url: 'https://polygon-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607' + }, + oneRpc: { + name: '1RPC', + url: 'https://1rpc.io/matic' + } + }, + tokens: { + matic: { + instanceAddress: { + '100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD', + '1000': '0xdf231d99Ff8b6c6CBF4E9B9a945CBAcEF9339178', + '10000': '0xaf4c0B70B2Ea9FB7487C7CbB37aDa259579fe040', + '100000': '0xa5C2254e4253490C54cef0a4347fddb8f75A4998' + }, + symbol: 'MATIC', + decimals: 18 + } + }, + ensSubdomainKey: 'polygon-tornado', + pollInterval: 10, + constants: { + NOTE_ACCOUNT_BLOCK: 16257996, + ENCRYPTED_NOTES_BLOCK: 16257996 + }, + 'tornado-proxy-light.contract.tornadocash.eth': '0x0D5550d52428E7e3175bfc9550207e4ad3859b17' + }, + netId10: { + rpcCallRetryAttempt: 15, + gasPrices: { + instant: 0.001, + fast: 0.001, + standard: 0.001, + low: 0.001 + }, + nativeCurrency: 'eth', + currencyName: 'ETH', + explorerUrl: { + tx: 'https://optimistic.etherscan.io/tx/', + address: 'https://optimistic.etherscan.io/address/', + block: 'https://optimistic.etherscan.io/block/' + }, + merkleTreeHeight: 20, + emptyElement: '21663839004416932945382355908790599225266501822907911457504978515578255421292', + networkName: 'Optimism', + deployedBlock: 2243689, + multicall: '0x35A6Cdb2C9AD4a45112df4a04147EB07dFA01aB7', + echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + ovmGasPriceOracleContract: '0x420000000000000000000000000000000000000F', + rpcUrls: { + tornadoRPC: { + name: 'Tornado RPC', + url: 'https://tornadocash-rpc.com/op' + }, + chainnodes: { + name: 'Chainnodes RPC', + url: 'https://optimism-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607' + }, + oneRpc: { + name: '1RPC', + url: 'https://1rpc.io/op' + } + }, + tokens: { + eth: { + instanceAddress: { + '0.1': '0x84443CFd09A48AF6eF360C6976C5392aC5023a1F', + '1': '0xd47438C816c9E7f2E2888E060936a499Af9582b3', + '10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a', + '100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD' + }, + symbol: 'ETH', + decimals: 18 + } + }, + ensSubdomainKey: 'optimism-tornado', + pollInterval: 15, + constants: { + NOTE_ACCOUNT_BLOCK: 2243694, + ENCRYPTED_NOTES_BLOCK: 2243694 + }, + 'tornado-proxy-light.contract.tornadocash.eth': '0x0D5550d52428E7e3175bfc9550207e4ad3859b17' + }, + netId42161: { + rpcCallRetryAttempt: 15, + gasPrices: { + instant: 4, + fast: 3, + standard: 2.52, + low: 2.29 + }, + nativeCurrency: 'eth', + currencyName: 'ETH', + explorerUrl: { + tx: 'https://arbiscan.io/tx/', + address: 'https://arbiscan.io/address/', + block: 'https://arbiscan.io/block/' + }, + merkleTreeHeight: 20, + emptyElement: '21663839004416932945382355908790599225266501822907911457504978515578255421292', + networkName: 'Arbitrum One', + deployedBlock: 3430648, + multicall: '0x842eC2c7D803033Edf55E478F461FC547Bc54EB2', + echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + rpcUrls: { + tornadoRPC: { + name: 'Tornado RPC', + url: 'https://tornadocash-rpc.com/arbitrum' + }, + chainnodes: { + name: 'Chainnodes RPC', + url: 'https://arbitrum-one.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607' + }, + oneRpc: { + name: '1rpc', + url: 'https://1rpc.io/arb' + }, + Arbitrum: { + name: 'Arbitrum RPC', + url: 'https://arb1.arbitrum.io/rpc' + } + }, + tokens: { + eth: { + instanceAddress: { + '0.1': '0x84443CFd09A48AF6eF360C6976C5392aC5023a1F', + '1': '0xd47438C816c9E7f2E2888E060936a499Af9582b3', + '10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a', + '100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD' + }, + symbol: 'ETH', + decimals: 18 + } + }, + ensSubdomainKey: 'arbitrum-tornado', + pollInterval: 15, + constants: { + NOTE_ACCOUNT_BLOCK: 3430605, + ENCRYPTED_NOTES_BLOCK: 3430605 + }, + 'tornado-proxy-light.contract.tornadocash.eth': '0x0D5550d52428E7e3175bfc9550207e4ad3859b17' + }, + netId100: { + rpcCallRetryAttempt: 15, + gasPrices: { + instant: 6, + fast: 5, + standard: 4, + low: 1 + }, + nativeCurrency: 'xdai', + currencyName: 'xDAI', + explorerUrl: { + tx: 'https://blockscout.com/xdai/mainnet/tx/', + address: 'https://blockscout.com/xdai/mainnet/address/', + block: 'https://blockscout.com/xdai/mainnet/block/' + }, + merkleTreeHeight: 20, + emptyElement: '21663839004416932945382355908790599225266501822907911457504978515578255421292', + networkName: 'Gnosis Chain', + deployedBlock: 17754561, + multicall: '0xb5b692a88bdfc81ca69dcb1d924f59f0413a602a', + echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + rpcUrls: { + tornadoRPC: { + name: 'Tornado RPC', + url: 'https://tornadocash-rpc.com/gnosis' + }, + chainnodes: { + name: 'Chainnodes RPC', + url: 'https://gnosis-mainnet.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607' + }, + blockPi: { + name: 'BlockPi', + url: 'https://gnosis.blockpi.network/v1/rpc/public' + } + }, + tokens: { + xdai: { + instanceAddress: { + '100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD', + '1000': '0xdf231d99Ff8b6c6CBF4E9B9a945CBAcEF9339178', + '10000': '0xaf4c0B70B2Ea9FB7487C7CbB37aDa259579fe040', + '100000': '0xa5C2254e4253490C54cef0a4347fddb8f75A4998' + }, + symbol: 'xDAI', + decimals: 18 + } + }, + ensSubdomainKey: 'gnosis-tornado', + pollInterval: 15, + constants: { + NOTE_ACCOUNT_BLOCK: 17754564, + ENCRYPTED_NOTES_BLOCK: 17754564 + }, + 'tornado-proxy-light.contract.tornadocash.eth': '0x0D5550d52428E7e3175bfc9550207e4ad3859b17' + }, + netId43114: { + rpcCallRetryAttempt: 15, + gasPrices: { + instant: 225, + fast: 35, + standard: 25, + low: 25 + }, + nativeCurrency: 'avax', + currencyName: 'AVAX', + explorerUrl: { + tx: 'https://snowtrace.io/tx/', + address: 'https://snowtrace.io/address/', + block: 'https://snowtrace.io/block/' + }, + merkleTreeHeight: 20, + emptyElement: '21663839004416932945382355908790599225266501822907911457504978515578255421292', + networkName: 'Avalanche Mainnet', + deployedBlock: 4429818, + multicall: '0xe86e3989c74293Acc962156cd3F525c07b6a1B6e', + echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + rpcUrls: { + publicRpc: { + name: 'Avalanche RPC', + url: 'https://api.avax.network/ext/bc/C/rpc' + }, + meowRPC: { + name: 'Meow RPC', + url: 'https://avax.meowrpc.com' + }, + oneRPC: { + name: 'OneRPC', + url: 'https://1rpc.io/avax/c' + } + }, + tokens: { + avax: { + instanceAddress: { + '10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a', + '100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD', + '500': '0xaf8d1839c3c67cf571aa74B5c12398d4901147B3' + }, + symbol: 'AVAX', + decimals: 18 + } + }, + ensSubdomainKey: 'avalanche-tornado', + pollInterval: 10, + constants: { + NOTE_ACCOUNT_BLOCK: 4429813, + ENCRYPTED_NOTES_BLOCK: 4429813 + }, + 'tornado-proxy-light.contract.tornadocash.eth': '0x0D5550d52428E7e3175bfc9550207e4ad3859b17' + }, + netId11155111: { + rpcCallRetryAttempt: 15, + gasPrices: { + instant: 2, + fast: 2, + standard: 2, + low: 2 + }, + nativeCurrency: 'eth', + currencyName: 'ETH', + explorerUrl: { + tx: 'https://sepolia.etherscan.io/tx/', + address: 'https://sepolia.etherscan.io/address/', + block: 'https://sepolia.etherscan.io/block/' + }, + merkleTreeHeight: 20, + emptyElement: '21663839004416932945382355908790599225266501822907911457504978515578255421292', + networkName: 'Ethereum Sepolia', + deployedBlock: 5594395, + multicall: '0xcA11bde05977b3631167028862bE2a173976CA11', + echoContractAccount: '0xcDD1fc3F5ac2782D83449d3AbE80D6b7B273B0e5', + aggregatorContract: '0x4088712AC9fad39ea133cdb9130E465d235e9642', + rpcUrls: { + tornadoRPC: { + name: 'Tornado RPC', + url: 'https://tornadocash-rpc.com/sepolia' + }, + sepolia: { + name: 'Sepolia RPC', + url: 'https://rpc.sepolia.org' + }, + chainnodes: { + name: 'Chainnodes RPC', + url: 'https://sepolia.chainnodes.org/d692ae63-0a7e-43e0-9da9-fe4f4cc6c607' + } + }, + tokens: { + eth: { + instanceAddress: { + '0.1': '0x8C4A04d872a6C1BE37964A21ba3a138525dFF50b', + '1': '0x8cc930096B4Df705A007c4A039BDFA1320Ed2508', + '10': '0x8D10d506D29Fc62ABb8A290B99F66dB27Fc43585', + '100': '0x44c5C92ed73dB43888210264f0C8b36Fd68D8379' + }, + symbol: 'ETH', + decimals: 18 + }, + dai: { + instanceAddress: { + '100': '0x6921fd1a97441dd603a997ED6DDF388658daf754', + '1000': '0x50a637770F5d161999420F7d70d888DE47207145', + '10000': '0xecD649870407cD43923A816Cc6334a5bdf113621', + '100000': '0x73B4BD04bF83206B6e979BE2507098F92EDf4F90' + }, + tokenAddress: '0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357', + symbol: 'DAI', + decimals: 18, + gasLimit: '55000' + } + }, + ensSubdomainKey: 'sepolia-tornado', + pollInterval: 15, + constants: { + GOVERNANCE_BLOCK: 5594395, + NOTE_ACCOUNT_BLOCK: 5594395, + ENCRYPTED_NOTES_BLOCK: 5594395, + MINING_BLOCK_TIME: 15 + }, + 'torn.contract.tornadocash.eth': '0x3AE6667167C0f44394106E197904519D808323cA', + 'governance.contract.tornadocash.eth': '0xe5324cD7602eeb387418e594B87aCADee08aeCAD', + 'tornado-router.contract.tornadocash.eth': '0x1572AFE6949fdF51Cb3E0856216670ae9Ee160Ee' + } +}; + +module.exports = { blockSyncInterval, enabledChains, networkConfig }; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..4dd3328 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "tornado-offline-deposit", + "version": "1.0.0", + "description": "Command line tool to create Tornado Cash notes offline", + "main": "tornadoOffline.js", + "bin": { + "tornadoOffline": "tornadoOffline.js" + }, + "scripts": { + "lint": "eslint *.js", + "start": "node tornadoOffline.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Tornado Cash Community", + "license": "MIT", + "files": [ + "abi", + ".eslintrc.js", + ".gitignore", + "networkConfig.js", + "tornadoOffline.js", + "README.md" + ], + "dependencies": { + "bn.js": "^5.2.1", + "circomlibjs": "0.1.7", + "commander": "^12.1.0", + "ethers": "^6.13.1" + }, + "devDependencies": { + "eslint": "^8.57.0" + } +} diff --git a/tornadoOffline.js b/tornadoOffline.js new file mode 100644 index 0000000..028bc6d --- /dev/null +++ b/tornadoOffline.js @@ -0,0 +1,229 @@ +#!/usr/bin/env node +const fs = require('fs'); +const crypto = require('crypto').webcrypto; +const BN = require('bn.js'); +const { Command } = require('commander'); +const circomlibjs = require('circomlibjs'); +const { MaxUint256, Interface } = require('ethers'); + +const ERC20ABI = require('./abi/ERC20.abi.json'); +const RouterABI = require('./abi/TornadoProxy.abi.json'); +const { enabledChains, networkConfig } = require('./networkConfig'); +const { description, version } = require('./package.json'); + +const program = new Command(); + +const erc20Interface = new Interface(ERC20ABI); +const routerInterface = new Interface(RouterABI); + +const bytesToHex = (bytes) => '0x' + Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''); + +// Convert BE encoded bytes (Buffer | Uint8Array) array to BigInt +const bytesToBN = (bytes) => BigInt(bytesToHex(bytes)); + +// Convert LE encoded bytes (Buffer | Uint8Array) array to BigInt +// const leBuff2Int = (bytes) => new BN(bytes, 16, 'le'); + +// Convert BigInt to LE encoded Uint8Array type +const leInt2Buff = (bigint) => Uint8Array.from(new BN(bigint).toArray('le', 31)); + +const toFixedHex = (numberish, length = 32) => { + return '0x' + BigInt(numberish).toString(16).padStart(length * 2, '0'); +}; + +const rBigInt = (nbytes = 31) => bytesToBN(crypto.getRandomValues(new Uint8Array(nbytes))); + +// https://github.com/tornadocash/tornado-classic-ui/blob/master/services/pedersen.js +class Pedersen { + constructor() { + this.pedersenPromise = this.initPedersen(); + } + + async initPedersen() { + this.pedersenHash = await circomlibjs.buildPedersenHash(); + this.babyJub = this.pedersenHash.babyJub; + } + + async unpackPoint(buffer) { + await this.pedersenPromise; + return this.babyJub?.unpackPoint(this.pedersenHash?.hash(buffer)); + } + + toStringBuffer(bytes) { + return this.babyJub.F.toString(bytes); + } +} + +const pedersen = new Pedersen(); + +const buffPedersenHash = async (bytes) => { + const [hash] = await pedersen.unpackPoint(bytes); + return pedersen.toStringBuffer(hash); +}; + +const createDeposit = async ({ nullifier, secret }) => { + const preimage = new Uint8Array([...leInt2Buff(nullifier), ...leInt2Buff(secret)]); + const noteHex = toFixedHex(bytesToBN(preimage), 62); + const commitment = BigInt(await buffPedersenHash(preimage)); + const commitmentHex = toFixedHex(commitment); + const nullifierHash = BigInt(await buffPedersenHash(leInt2Buff(nullifier))); + const nullifierHex = toFixedHex(nullifierHash); + + return { + preimage, + noteHex, + commitment, + commitmentHex, + nullifierHash, + nullifierHex, + }; +}; + +/** + * Create Tornado Deposit Note and Invoice + */ +const createNote = async ({ netId, currency, amount }) => { + const nullifier = rBigInt(31); + const secret = rBigInt(31); + + const depositObject = await createDeposit({ + nullifier, + secret, + }); + + return { + currency, + amount, + netId, + note: `tornado-${currency}-${amount}-${netId}-${depositObject.noteHex}`, + invoice: `tornadoInvoice-${currency}-${amount}-${netId}-${depositObject.commitmentHex}`, + nullifier, + secret, + ...depositObject, + }; +}; + +const getInstanceInfo = ({ netId, currency, amount }) => { + const networkObject = networkConfig[`netId${netId}`]; + + const routerAddress = networkObject['tornado-router.contract.tornadocash.eth'] + || networkObject['tornado-proxy-light.contract.tornadocash.eth'] + || networkObject['tornado-proxy.contract.tornadocash.eth']; + + const instanceAddress = networkObject.tokens[currency]?.instanceAddress[amount]; + + const instanceDecimals = Number(networkObject.tokens[currency]?.decimals); + + const tokenAddress = networkObject.tokens[currency]?.tokenAddress; + + const isNativeCurrency = networkObject.nativeCurrency === currency; + + if (!instanceAddress) { + const errMsg = `Could not find a tornado instance ${netId} ${currency} ${amount}`; + throw new Error(errMsg); + } + + const denomination = BigInt(amount * 10 ** instanceDecimals); + + return { + routerAddress, + instanceAddress, + instanceDecimals, + tokenAddress, + isNativeCurrency, + denomination, + }; +}; + +const newDeposit = async ({ netId, currency, amount }) => { + currency = String(currency).toLowerCase(); + amount = Number(amount); + netId = Number(netId); + + if (!enabledChains.includes(netId.toString())) { + const errMsg = `Unsupported chain ${netId}`; + throw new Error(errMsg); + } + + const { + routerAddress, + instanceAddress, + tokenAddress, + isNativeCurrency, + denomination, + } = getInstanceInfo({ netId, currency, amount }); + + const { + note, + noteHex, + invoice, + commitmentHex: commitment, + nullifierHex: nullifierHash + } = await createNote({ netId, currency, amount }); + + const depositData = routerInterface.encodeFunctionData('deposit', [instanceAddress, commitment, '0x']); + + console.log( + `New deposit: ${JSON.stringify({ + note, + invoice, + commitment, + nullifierHash + }, null, 2)}\n` + ); + + // Backup locally + fs.writeFileSync(`./backup-tornado-${currency}-${amount}-${netId}-${noteHex.slice(0, 10)}.txt`, note, { encoding: 'utf8' }); + + if (isNativeCurrency) { + console.log( + `Transaction Data: ${JSON.stringify({ + to: routerAddress, + value: denomination.toString(), + data: depositData + }, null, 2)}` + ); + return; + } + + const approveData = erc20Interface.encodeFunctionData('approve', [routerAddress, MaxUint256]); + + console.log( + `Approve Data: ${JSON.stringify({ + to: tokenAddress, + data: approveData + }, null, 2)}]\n` + ); + + console.log( + `Transaction Data: ${JSON.stringify({ + to: routerAddress, + data: depositData + }, null, 2)}` + ); +}; + +program + .name('tornadoOffline') + .description(description) + .version(version) + .argument('', 'netId of the supported network (Ethereum Mainnet: 1, Goerli Testnet: 5, BSC: 56)') + .argument('', 'Native Currency or Token supported by Tornado Cash') + .argument('', 'Amount to deposit (Check the UI for supported amount)') + .action((netId, currency, amount) => { + console.log('Creating offline Tornado Cash Note\n'); + newDeposit({ netId, currency, amount }); + }); + +program + .command('list') + .description('List tornado cash backup notes on local') + .action(() => { + const backups = fs.readdirSync('.').filter(f => f.includes('backup')); + + const context = backups.map(b => fs.readFileSync(b, { encoding: 'utf8' })).join('\n'); + + console.log(context); + }); + +program.parse(); \ No newline at end of file