Merge branch 'master' into fixTorando

This commit is contained in:
0xZick 地方分権化 2021-07-16 13:30:07 +03:00 committed by GitHub
commit 4dbdd42252
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 139 additions and 72 deletions

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"semi": false,
"printWidth": 130
}

View File

@ -7,24 +7,25 @@ Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medi
Example: Example:
```bash ```bash
./cli.js deposit ETH 0.1 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448 $ ./cli.js deposit ETH 0.1 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448
Your note: tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
Tornado ETH balance is 8.9
Sender account ETH balance is 1004873.470619891361352542
Submitting deposit transaction
Tornado ETH balance is 9
Sender account ETH balance is 1004873.361652048361352542
``` ```
> Your note: tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
> Tornado ETH balance is 8.9
> Sender account ETH balance is 1004873.470619891361352542
> Submitting deposit transaction
> Tornado ETH balance is 9
> Sender account ETH balance is 1004873.361652048361352542
```bash ```bash
./cli.js withdraw tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448 --relayer https://kovan-frelay.duckdns.org $ ./cli.js withdraw tornado-eth-0.1-42-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://kovan.infura.io/v3/27a9649f826b4e31a83e07ae09a87448 --relayer https://kovan-frelay.duckdns.org
```
> Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754 Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
> Getting current state from tornado contract Getting current state from tornado contract
> Generating SNARK proof Generating SNARK proof
> Proof time: 9117.051ms Proof time: 9117.051ms
> Sending withdraw transaction through relay Sending withdraw transaction through relay
> Transaction submitted through the relay. View transaction on etherscan https://kovan.etherscan.io/tx/0xcb21ae8cad723818c6bc7273e83e00c8393fcdbe74802ce5d562acad691a2a7b Transaction submitted through the relay. View transaction on etherscan https://kovan.etherscan.io/tx/0xcb21ae8cad723818c6bc7273e83e00c8393fcdbe74802ce5d562acad691a2a7b
> Transaction mined in block 17036120 Transaction mined in block 17036120
> Done Done
```

169
cli.js
View File

@ -23,14 +23,14 @@ let web3, tornado, mixerContract, tornadoInstance, circuit, proving_key, groth16
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, PRIVATE_KEY let MERKLE_TREE_HEIGHT, ETH_AMOUNT, TOKEN_AMOUNT, PRIVATE_KEY
/** Whether we are in a browser or node.js */ /** Whether we are in a browser or node.js */
const inBrowser = (typeof window !== 'undefined') const inBrowser = typeof window !== 'undefined'
let isLocalRPC = false let isLocalRPC = false
/** Generate random number of specified byte length */ /** Generate random number of specified byte length */
const rbigint = nbytes => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes)) const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
/** Compute pedersen hash */ /** Compute pedersen hash */
const pedersenHash = data => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0] const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0]
/** BigNumber to hex string of specified length */ /** BigNumber to hex string of specified length */
function toHex(number, length = 32) { function toHex(number, length = 32) {
@ -69,7 +69,10 @@ function createDeposit({ nullifier, secret }) {
* @param amount Deposit amount * @param amount Deposit amount
*/ */
async function deposit({ currency, amount }) { async function deposit({ currency, amount }) {
const deposit = createDeposit({ nullifier: rbigint(31), secret: rbigint(31) }) const deposit = createDeposit({
nullifier: rbigint(31),
secret: rbigint(31)
})
const note = toHex(deposit.preimage, 62) const note = toHex(deposit.preimage, 62)
const noteString = `tornado-${currency}-${amount}-${netId}-${note}` const noteString = `tornado-${currency}-${amount}-${netId}-${note}`
console.log(`Your note: ${noteString}`) console.log(`Your note: ${noteString}`)
@ -81,7 +84,8 @@ async function deposit({ currency, amount }) {
await tornado.methods.deposit(tornadoInstance, toHex(deposit.commitment), []).send({ value, from: senderAccount, gas: 2e6 }) await tornado.methods.deposit(tornadoInstance, toHex(deposit.commitment), []).send({ value, from: senderAccount, gas: 2e6 })
await printETHBalance({ address: tornado._address, name: 'Tornado' }) await printETHBalance({ address: tornado._address, name: 'Tornado' })
await printETHBalance({ address: senderAccount, name: 'Sender account' }) await printETHBalance({ address: senderAccount, name: 'Sender account' })
} else { // a token } else {
// a token
await printERC20Balance({ address: tornado._address, name: 'Tornado' }) await printERC20Balance({ address: tornado._address, name: 'Tornado' })
await printERC20Balance({ address: senderAccount, name: 'Sender account' }) await printERC20Balance({ address: senderAccount, name: 'Sender account' })
const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
@ -116,6 +120,7 @@ async function deposit({ currency, amount }) {
async function generateMerkleProof(deposit, amount) { async function generateMerkleProof(deposit, amount) {
let leafIndex = -1 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 = loadCachedEvents({ type: 'Deposit', amount }) const cachedEvents = loadCachedEvents({ type: 'Deposit', amount })
const startBlock = cachedEvents.lastBlock const startBlock = cachedEvents.lastBlock
@ -189,7 +194,7 @@ async function generateProof({ deposit, amount, recipient, relayerAddress = 0, f
nullifier: deposit.nullifier, nullifier: deposit.nullifier,
secret: deposit.secret, secret: deposit.secret,
pathElements: path_elements, pathElements: path_elements,
pathIndices: path_index, pathIndices: path_index
} }
console.log('Generating SNARK proof') console.log('Generating SNARK proof')
@ -227,13 +232,21 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
const relayerStatus = await axios.get(relayerURL + '/status') const relayerStatus = await axios.get(relayerURL + '/status')
const { rewardAccount, netId, ethPrices, tornadoServiceFee } = relayerStatus.data const { rewardAccount, netId, ethPrices, tornadoServiceFee } = relayerStatus.data
assert(netId === await web3.eth.net.getId() || netId === '*', 'This relay is for different network') assert(netId === (await web3.eth.net.getId()) || netId === '*', 'This relay is for different network')
console.log('Relay address: ', rewardAccount) console.log('Relay address: ', rewardAccount)
const gasPrice = await fetchGasPrice() const gasPrice = await fetchGasPrice()
const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals const decimals = isLocalRPC ? 18 : config.deployments[`netId${netId}`][currency].decimals
const fee = calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerServiceFee: tornadoServiceFee, decimals }) const fee = calculateFee({
currency,
gasPrice,
amount,
refund,
ethPrices,
relayerServiceFee: tornadoServiceFee,
decimals
})
if (fee.gt(fromDecimals({ amount, decimals }))) { if (fee.gt(fromDecimals({ amount, decimals }))) {
throw new Error('Too high refund') throw new Error('Too high refund')
} }
@ -242,7 +255,11 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
console.log('Sending withdraw transaction through relay') console.log('Sending withdraw transaction through relay')
try { try {
const response = await axios.post(relayerURL + '/v1/tornadoWithdraw', { contract: tornadoInstance, proof, args }) const response = await axios.post(relayerURL + '/v1/tornadoWithdraw', {
contract: tornadoInstance,
proof,
args
})
const { id } = response.data const { id } = response.data
@ -255,25 +272,29 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
console.error(e.message) console.error(e.message)
} }
} }
} else { // using private key } else {
// using private key
const { proof, args } = await generateProof({ deposit, recipient, refund }) const { proof, args } = await generateProof({ deposit, recipient, refund })
console.log('Submitting withdraw transaction') console.log('Submitting withdraw transaction')
await tornado.methods.withdraw(tornadoInstance, proof, ...args).send({ from: senderAccount, value: refund.toString(), gas: 1e6 }) await tornado.methods
.withdraw(tornadoInstance, proof, ...args)
.send({ from: senderAccount, value: refund.toString(), gas: 1e6 })
.on('transactionHash', function (txHash) { .on('transactionHash', function (txHash) {
if (netId === 1 || netId === 42) { if (netId === 1 || netId === 42) {
console.log(`View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`) console.log(`View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`)
} else { } else {
console.log(`The transaction hash is ${txHash}`) console.log(`The transaction hash is ${txHash}`)
} }
}).on('error', function (e) { })
.on('error', function (e) {
console.error('on transactionHash error', e.message) console.error('on transactionHash error', e.message)
}) })
} }
console.log('Done') console.log('Done')
} }
function getStatus (id, relayerURL) { function getStatus(id, relayerURL) {
return new Promise((resolve) => { return new Promise((resolve) => {
async function getRelayerStatus() { async function getRelayerStatus() {
const responseStatus = await axios.get(relayerURL + '/v1/jobs/' + id) const responseStatus = await axios.get(relayerURL + '/v1/jobs/' + id)
@ -289,7 +310,9 @@ function getStatus (id, relayerURL) {
if (status === 'CONFIRMED') { if (status === 'CONFIRMED') {
const receipt = await waitForTxReceipt({ txHash }) const receipt = await waitForTxReceipt({ txHash })
console.log(`Transaction submitted through the relay. View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`) console.log(
`Transaction submitted through the relay. View transaction on etherscan https://${getCurrentNetworkName()}etherscan.io/tx/${txHash}`
)
console.log('Transaction mined in block', receipt.blockNumber) console.log('Transaction mined in block', receipt.blockNumber)
resolve(status) resolve(status)
} }
@ -322,9 +345,7 @@ function fromDecimals({ amount, decimals }) {
// Split it into a whole and fractional part // Split it into a whole and fractional part
const comps = ether.split('.') const comps = ether.split('.')
if (comps.length > 2) { if (comps.length > 2) {
throw new Error( throw new Error('[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points')
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal points'
)
} }
let whole = comps[0] let whole = comps[0]
@ -337,9 +358,7 @@ function fromDecimals({ amount, decimals }) {
fraction = '0' fraction = '0'
} }
if (fraction.length > baseLength) { if (fraction.length > baseLength) {
throw new Error( throw new Error('[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places')
'[ethjs-unit] while converting number ' + amount + ' to wei, too many decimal places'
)
} }
while (fraction.length < baseLength) { while (fraction.length < baseLength) {
@ -394,14 +413,13 @@ function toDecimals(value, decimals, fixed) {
function getCurrentNetworkName() { function getCurrentNetworkName() {
switch (netId) { switch (netId) {
case 1: case 1:
return '' return ''
case 5: case 5:
return 'goerli.' return 'goerli.'
case 42: case 42:
return 'kovan.' return 'kovan.'
} }
} }
function gasPrices(value = 80) { function gasPrices(value = 80) {
@ -423,26 +441,26 @@ async function fetchGasPrice() {
} }
function calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerServiceFee, decimals }) { function calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerServiceFee, decimals }) {
const decimalsPoint = Math.floor(relayerServiceFee) === Number(relayerServiceFee) ? const decimalsPoint =
0 : Math.floor(relayerServiceFee) === Number(relayerServiceFee) ? 0 : relayerServiceFee.toString().split('.')[1].length
relayerServiceFee.toString().split('.')[1].length
const roundDecimal = 10 ** decimalsPoint const roundDecimal = 10 ** decimalsPoint
const total = toBN(fromDecimals({ amount, decimals })) const total = toBN(fromDecimals({ amount, decimals }))
const feePercent = total.mul(toBN(relayerServiceFee * roundDecimal)).div(toBN(roundDecimal * 100)) const feePercent = total.mul(toBN(relayerServiceFee * roundDecimal)).div(toBN(roundDecimal * 100))
const expense = toBN(gasPrice).mul(toBN(5e5)) const expense = toBN(gasPrice).mul(toBN(5e5))
let desiredFee let desiredFee
switch (currency) { switch (currency) {
case 'eth': { case 'eth': {
desiredFee = expense.add(feePercent) desiredFee = expense.add(feePercent)
break break
} }
default: { default: {
desiredFee = expense.add(toBN(refund)) desiredFee = expense
.mul(toBN(10 ** decimals)) .add(toBN(refund))
.div(toBN(ethPrices[currency])) .mul(toBN(10 ** decimals))
desiredFee = desiredFee.add(feePercent) .div(toBN(ethPrices[currency]))
break desiredFee = desiredFee.add(feePercent)
} break
}
} }
return desiredFee return desiredFee
} }
@ -512,7 +530,12 @@ function parseNote(noteString) {
const deposit = createDeposit({ nullifier, secret }) const deposit = createDeposit({ nullifier, secret })
const netId = Number(match.groups.netId) const netId = Number(match.groups.netId)
return { currency: match.groups.currency, amount: match.groups.amount, netId, deposit } return {
currency: match.groups.currency,
amount: match.groups.amount,
netId,
deposit
}
} }
async function loadDepositData({ deposit }) { async function loadDepositData({ deposit }) {
@ -533,7 +556,13 @@ async function loadDepositData({ deposit }) {
const isSpent = await tornado.methods.isSpent(deposit.nullifierHex).call() const isSpent = await tornado.methods.isSpent(deposit.nullifierHex).call()
const receipt = await web3.eth.getTransactionReceipt(txHash) const receipt = await web3.eth.getTransactionReceipt(txHash)
return { timestamp, txHash, isSpent, from: receipt.from, commitment: deposit.commitmentHex } return {
timestamp,
txHash,
isSpent,
from: receipt.from,
commitment: deposit.commitmentHex
}
} catch (e) { } catch (e) {
console.error('loadDepositData', e) console.error('loadDepositData', e)
} }
@ -569,9 +598,7 @@ async function loadWithdrawalData({ amount, currency, deposit }) {
const fee = withdrawEvent.fee const fee = withdrawEvent.fee
const decimals = config.deployments[`netId${netId}`][currency].decimals const decimals = config.deployments[`netId${netId}`][currency].decimals
const withdrawalAmount = toBN(fromDecimals({ amount, decimals })).sub( const withdrawalAmount = toBN(fromDecimals({ amount, decimals })).sub(toBN(fee))
toBN(fee)
)
const { timestamp } = await web3.eth.getBlock(withdrawEvent.blockHash) const { timestamp } = await web3.eth.getBlock(withdrawEvent.blockHash)
return { return {
amount: toDecimals(withdrawalAmount, decimals, 9), amount: toDecimals(withdrawalAmount, decimals, 9),
@ -595,7 +622,9 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
if (inBrowser) { if (inBrowser) {
// Initialize using injected web3 (Metamask) // Initialize using injected web3 (Metamask)
// To assemble web version run `npm run browserify` // To assemble web version run `npm run browserify`
web3 = new Web3(window.web3.currentProvider, null, { transactionConfirmationBlocks: 1 }) web3 = new Web3(window.web3.currentProvider, null, {
transactionConfirmationBlocks: 1
})
contractJson = await (await fetch('build/contracts/TornadoProxy.abi.json')).json() contractJson = await (await fetch('build/contracts/TornadoProxy.abi.json')).json()
mixerJson = await (await fetch('build/contracts/Mixer.abi.json')).json() mixerJson = await (await fetch('build/contracts/Mixer.abi.json')).json()
circuit = await (await fetch('build/circuits/tornado.json')).json() circuit = await (await fetch('build/circuits/tornado.json')).json()
@ -678,7 +707,9 @@ async function main() {
.option('-R, --relayer <URL>', 'Withdraw via relayer') .option('-R, --relayer <URL>', 'Withdraw via relayer')
program program
.command('deposit <currency> <amount>') .command('deposit <currency> <amount>')
.description('Submit a deposit of specified currency and amount from default eth account and return the resulting note. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.') .description(
'Submit a deposit of specified currency and amount from default eth account and return the resulting note. The currency is one of (ETH|DAI|cDAI|USDC|cUSDC|USDT). The amount depends on currency, see config.js file or visit https://tornado.cash.'
)
.action(async (currency, amount) => { .action(async (currency, amount) => {
currency = currency.toLowerCase() currency = currency.toLowerCase()
await init({ rpc: program.rpc, currency, amount }) await init({ rpc: program.rpc, currency, amount })
@ -686,11 +717,20 @@ async function main() {
}) })
program program
.command('withdraw <note> <recipient> [ETH_purchase]') .command('withdraw <note> <recipient> [ETH_purchase]')
.description('Withdraw a note to a recipient account using relayer or specified private key. You can exchange some of your deposit`s tokens to ETH during the withdrawal by specifing ETH_purchase (e.g. 0.01) to pay for gas in future transactions. Also see the --relayer option.') .description(
'Withdraw a note to a recipient account using relayer or specified private key. You can exchange some of your deposit`s tokens to ETH during the withdrawal by specifing ETH_purchase (e.g. 0.01) to pay for gas in future transactions. Also see the --relayer option.'
)
.action(async (noteString, recipient, refund) => { .action(async (noteString, recipient, refund) => {
const { currency, amount, netId, deposit } = parseNote(noteString) const { currency, amount, netId, deposit } = parseNote(noteString)
await init({ rpc: program.rpc, noteNetId: netId, currency, amount }) await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
await withdraw({ deposit, currency, amount, recipient, refund, relayerURL: program.relayer }) await withdraw({
deposit,
currency,
amount,
recipient,
refund,
relayerURL: program.relayer
})
}) })
program program
.command('balance <address> [token_address]') .command('balance <address> [token_address]')
@ -704,7 +744,9 @@ async function main() {
}) })
program program
.command('compliance <note>') .command('compliance <note>')
.description('Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.') .description(
'Shows the deposit and withdrawal of the provided note. This might be necessary to show the origin of assets held in your withdrawal address.'
)
.action(async (noteString) => { .action(async (noteString) => {
const { currency, amount, netId, deposit } = parseNote(noteString) const { currency, amount, netId, deposit } = parseNote(noteString)
await init({ rpc: program.rpc, noteNetId: netId, currency, amount }) await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
@ -720,7 +762,11 @@ async function main() {
console.log('The note was not spent') console.log('The note was not spent')
} }
const withdrawInfo = await loadWithdrawalData({ amount, currency, deposit }) const withdrawInfo = await loadWithdrawalData({
amount,
currency,
deposit
})
const withdrawalDate = new Date(withdrawInfo.timestamp * 1000) const withdrawalDate = new Date(withdrawInfo.timestamp * 1000)
console.log('\n=============Withdrawal==============') console.log('\n=============Withdrawal==============')
console.log('Withdrawal :', withdrawInfo.amount, currency) console.log('Withdrawal :', withdrawInfo.amount, currency)
@ -740,15 +786,28 @@ async function main() {
await init({ rpc: program.rpc, currency, amount }) await init({ rpc: program.rpc, currency, amount })
let noteString = await deposit({ currency, amount }) let noteString = await deposit({ currency, amount })
let parsedNote = parseNote(noteString) let parsedNote = parseNote(noteString)
await withdraw({ deposit: parsedNote.deposit, currency, amount, recipient: senderAccount, relayerURL: program.relayer }) await withdraw({
deposit: parsedNote.deposit,
currency,
amount,
recipient: senderAccount,
relayerURL: program.relayer
})
console.log('\nStart performing DAI deposit-withdraw test') console.log('\nStart performing DAI deposit-withdraw test')
currency = 'dai' currency = 'dai'
amount = '100' amount = '100'
await init({ rpc: program.rpc, currency, amount }) await init({ rpc: program.rpc, currency, amount })
noteString = await deposit({ currency, amount }) noteString = await deposit({ currency, amount })
; (parsedNote = parseNote(noteString)) parsedNote = parseNote(noteString)
await withdraw({ deposit: parsedNote.deposit, currency, amount, recipient: senderAccount, refund: '0.02', relayerURL: program.relayer }) await withdraw({
deposit: parsedNote.deposit,
currency,
amount,
recipient: senderAccount,
refund: '0.02',
relayerURL: program.relayer
})
}) })
try { try {
await program.parseAsync(process.argv) await program.parseAsync(process.argv)