Compare commits

...

2 Commits

Author SHA1 Message Date
Alexey
340f4f596c version 2021-02-16 18:54:15 +03:00
Alexey
62758b3e61 init 2021-02-14 23:40:23 +03:00
4 changed files with 318 additions and 287 deletions

429
cli.js

@ -3,41 +3,23 @@
// Works both in browser and node.js // Works both in browser and node.js
require('dotenv').config() require('dotenv').config()
const fs = require('fs')
const axios = require('axios') const axios = require('axios')
const assert = require('assert') const assert = require('assert')
const snarkjs = require('snarkjs')
const crypto = require('crypto')
const circomlib = require('circomlib')
const bigInt = snarkjs.bigInt
const merkleTree = require('./lib/MerkleTree')
const Web3 = require('web3') const Web3 = require('web3')
const buildGroth16 = require('websnark/src/groth16')
const websnarkUtils = require('websnark/src/utils')
const { toWei, fromWei, toBN, BN } = require('web3-utils') const { toWei, fromWei, toBN, BN } = require('web3-utils')
const config = require('./config') const config = require('./config')
const program = require('commander') const program = require('commander')
const { GasPriceOracle } = require('gas-price-oracle') const { GasPriceOracle } = require('gas-price-oracle')
let web3, tornado, mixerContract, tornadoInstance, circuit, proving_key, groth16, erc20, senderAccount, netId const { initialize, createDeposit, generateProof, toHex, rbigint, bigInt } = require('./core')
let web3, tornado, mixerContract, tornadoInstance, erc20, senderAccount, netId
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 */
const inBrowser = typeof window !== 'undefined'
let isLocalRPC = false let isLocalRPC = false
/** 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]
/** BigNumber to hex string of specified length */
function toHex(number, length = 32) {
const str = number instanceof Buffer ? number.toString('hex') : bigInt(number).toString(16)
return '0x' + str.padStart(length * 2, '0')
}
/** Display ETH account balance */ /** Display ETH account balance */
async function printETHBalance({ address, name }) { async function printETHBalance({ address, name }) {
console.log(`${name} ETH balance is`, web3.utils.fromWei(await web3.eth.getBalance(address))) console.log(`${name} ETH balance is`, web3.utils.fromWei(await web3.eth.getBalance(address)))
@ -50,19 +32,6 @@ async function printERC20Balance({ address, name, tokenAddress }) {
console.log(`${name} Token Balance is`, web3.utils.fromWei(await erc20.methods.balanceOf(address).call())) console.log(`${name} Token Balance is`, web3.utils.fromWei(await erc20.methods.balanceOf(address).call()))
} }
/**
* Create deposit object from secret and nullifier
*/
function createDeposit({ nullifier, secret }) {
const deposit = { nullifier, secret }
deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
deposit.commitment = pedersenHash(deposit.preimage)
deposit.commitmentHex = toHex(deposit.commitment)
deposit.nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
deposit.nullifierHex = toHex(deposit.nullifierHash)
return deposit
}
/** /**
* Make a deposit * Make a deposit
* @param currency Сurrency * @param currency Сurrency
@ -111,97 +80,17 @@ async function deposit({ currency, amount }) {
return noteString return noteString
} }
/**
* Generate merkle tree for a deposit.
* Download deposit events from the tornado, reconstructs merkle tree, finds our deposit leaf
* in it and generates merkle proof
* @param deposit Deposit object
*/
async function generateMerkleProof(deposit) {
let leafIndex = -1
// Get all deposit events from smart contract and assemble merkle tree from them
const events = await mixerContract.getPastEvents('Deposit', {
fromBlock: 0,
toBlock: 'latest'
})
const leaves = events
.sort((a, b) => a.returnValues.leafIndex - b.returnValues.leafIndex) // Sort events in chronological order
.map((e) => {
const index = toBN(e.returnValues.leafIndex).toNumber()
if (toBN(e.returnValues.commitment).eq(toBN(deposit.commitmentHex))) {
leafIndex = index
}
return e.returnValues.commitment.toString(10)
})
const tree = new merkleTree(MERKLE_TREE_HEIGHT, leaves)
// Validate that our data is correct
const root = await tree.root()
const isValidRoot = await mixerContract.methods.isKnownRoot(toHex(root)).call()
const isSpent = await mixerContract.methods.isSpent(toHex(deposit.nullifierHash)).call()
assert(isValidRoot === true, 'Merkle tree is corrupted')
assert(isSpent === false, 'The note is already spent')
assert(leafIndex >= 0, 'The deposit is not found in the tree')
// Compute merkle proof of our commitment
return tree.path(leafIndex)
}
/**
* Generate SNARK proof for withdrawal
* @param deposit Deposit object
* @param recipient Funds recipient
* @param relayer Relayer address
* @param fee Relayer fee
* @param refund Receive ether for exchanged tokens
*/
async function generateProof({ deposit, recipient, relayerAddress = 0, fee = 0, refund = 0 }) {
// Compute merkle proof of our commitment
const { root, path_elements, path_index } = await generateMerkleProof(deposit)
// Prepare circuit input
const input = {
// Public snark inputs
root: root,
nullifierHash: deposit.nullifierHash,
recipient: bigInt(recipient),
relayer: bigInt(relayerAddress),
fee: bigInt(fee),
refund: bigInt(refund),
// Private snark inputs
nullifier: deposit.nullifier,
secret: deposit.secret,
pathElements: path_elements,
pathIndices: path_index
}
console.log('Generating SNARK proof')
console.time('Proof time')
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { proof } = websnarkUtils.toSolidityInput(proofData)
console.timeEnd('Proof time')
const args = [
toHex(input.root),
toHex(input.nullifierHash),
toHex(input.recipient, 20),
toHex(input.relayer, 20),
toHex(input.fee),
toHex(input.refund)
]
return { proof, args }
}
/** /**
* Do an ETH withdrawal * Do an ETH withdrawal
* @param noteString Note to withdraw * @param noteString Note to withdraw
* @param recipient Recipient address * @param recipient Recipient address
*/ */
async function withdraw({ deposit, currency, amount, recipient, relayerURL, refund = '0' }) { async function withdraw({ deposit, currency, amount, recipient, relayerURL, refund = '0' }) {
// Get all deposit events from smart contract and assemble merkle tree from them
const events = await mixerContract.getPastEvents('Deposit', {
fromBlock: 0,
toBlock: 'latest'
})
if (currency === 'eth' && refund !== '0') { if (currency === 'eth' && refund !== '0') {
throw new Error('The ETH purchase is supposted to be 0 for ETH withdrawals') throw new Error('The ETH purchase is supposted to be 0 for ETH withdrawals')
} }
@ -237,9 +126,17 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
recipient, recipient,
relayerAddress: rewardAccount, relayerAddress: rewardAccount,
fee, fee,
refund refund,
events
}) })
// Validate that our data is correct
// const isValidRoot = await mixerContract.methods.isKnownRoot(toHex(root)).call()
// const isSpent = await mixerContract.methods.isSpent(toHex(deposit.nullifierHash)).call()
// assert(isValidRoot === true, 'Merkle tree is corrupted')
// assert(isSpent === false, 'The note is already spent')
// assert(leafIndex >= 0, 'The deposit is not found in the tree')
console.log('Sending withdraw transaction through relay') console.log('Sending withdraw transaction through relay')
try { try {
const response = await axios.post(relayerURL + '/v1/tornadoWithdraw', { const response = await axios.post(relayerURL + '/v1/tornadoWithdraw', {
@ -261,7 +158,7 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, refu
} }
} else { } else {
// using private key // using private key
const { proof, args } = await generateProof({ deposit, recipient, refund }) const { proof, args } = await generateProof({ deposit, recipient, refund, events })
console.log('Submitting withdraw transaction') console.log('Submitting withdraw transaction')
await tornado.methods await tornado.methods
@ -564,45 +461,27 @@ async function loadWithdrawalData({ amount, currency, deposit }) {
*/ */
async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) { async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
let contractJson, mixerJson, erc20ContractJson, erc20tornadoJson, tornadoAddress, tokenAddress let contractJson, mixerJson, erc20ContractJson, erc20tornadoJson, tornadoAddress, tokenAddress
// TODO do we need this? should it work in browser really?
if (inBrowser) { web3 = new Web3(rpc, null, { transactionConfirmationBlocks: 1 })
// Initialize using injected web3 (Metamask) contractJson = require('./build/contracts/TornadoProxy.abi.json')
// To assemble web version run `npm run browserify` mixerJson = require('./build/contracts/Mixer.abi.json')
web3 = new Web3(window.web3.currentProvider, null, { MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20
transactionConfirmationBlocks: 1 await initialize({ merkleTreeHeight: MERKLE_TREE_HEIGHT })
})
contractJson = await (await fetch('build/contracts/TornadoProxy.abi.json')).json() ETH_AMOUNT = process.env.ETH_AMOUNT
mixerJson = await (await fetch('build/contracts/Mixer.abi.json')).json() TOKEN_AMOUNT = process.env.TOKEN_AMOUNT
circuit = await (await fetch('build/circuits/tornado.json')).json() PRIVATE_KEY = process.env.PRIVATE_KEY
proving_key = await (await fetch('build/circuits/tornadoProvingKey.bin')).arrayBuffer() if (PRIVATE_KEY) {
MERKLE_TREE_HEIGHT = 20 const account = web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
ETH_AMOUNT = 1e18 web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
TOKEN_AMOUNT = 1e19 web3.eth.defaultAccount = account.address
senderAccount = (await web3.eth.getAccounts())[0] senderAccount = account.address
} else { } else {
// Initialize from local node console.log('Warning! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit')
web3 = new Web3(rpc, null, { transactionConfirmationBlocks: 1 })
contractJson = require('./build/contracts/TornadoProxy.abi.json')
mixerJson = require('./build/contracts/Mixer.abi.json')
circuit = require('./build/circuits/tornado.json')
proving_key = fs.readFileSync('build/circuits/tornadoProvingKey.bin').buffer
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20
ETH_AMOUNT = process.env.ETH_AMOUNT
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT
PRIVATE_KEY = process.env.PRIVATE_KEY
if (PRIVATE_KEY) {
const account = web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
web3.eth.defaultAccount = account.address
senderAccount = account.address
} else {
console.log('Warning! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you deposit')
}
erc20ContractJson = require('./build/contracts/ERC20Mock.json')
erc20tornadoJson = require('./build/contracts/ERC20Tornado.json')
} }
// groth16 initialises a lot of Promises that will never be resolved, that's why we need to use process.exit to terminate the CLI erc20ContractJson = require('./build/contracts/ERC20Mock.json')
groth16 = await buildGroth16() erc20tornadoJson = require('./build/contracts/ERC20Tornado.json')
netId = await web3.eth.net.getId() netId = await web3.eth.net.getId()
if (noteNetId && Number(noteNetId) !== netId) { if (noteNetId && Number(noteNetId) !== netId) {
throw new Error('This note is for a different network. Specify the --rpc option explicitly') throw new Error('This note is for a different network. Specify the --rpc option explicitly')
@ -633,135 +512,119 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100' }) {
} }
async function main() { async function main() {
if (inBrowser) { program
const instance = { currency: 'eth', amount: '0.1' } .option('-r, --rpc <URL>', 'The RPC, CLI should interact with', 'http://localhost:8545')
await init(instance) .option('-R, --relayer <URL>', 'Withdraw via relayer')
window.deposit = async () => { program
await deposit(instance) .command('deposit <currency> <amount>')
} .description(
window.withdraw = async () => { '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.'
const noteString = prompt('Enter the note to withdraw') )
const recipient = (await web3.eth.getAccounts())[0] .action(async (currency, amount) => {
currency = currency.toLowerCase()
await init({ rpc: program.rpc, currency, amount })
await deposit({ currency, amount })
})
program
.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.'
)
.action(async (noteString, recipient, refund) => {
const { currency, amount, netId, deposit } = parseNote(noteString) const { currency, amount, netId, deposit } = parseNote(noteString)
await init({ noteNetId: netId, currency, amount }) await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
await withdraw({ deposit, currency, amount, recipient }) await withdraw({
} deposit,
} else { currency,
program amount,
.option('-r, --rpc <URL>', 'The RPC, CLI should interact with', 'http://localhost:8545') recipient,
.option('-R, --relayer <URL>', 'Withdraw via relayer') refund,
program relayerURL: program.relayer
.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.'
)
.action(async (currency, amount) => {
currency = currency.toLowerCase()
await init({ rpc: program.rpc, currency, amount })
await deposit({ currency, amount })
}) })
program })
.command('withdraw <note> <recipient> [ETH_purchase]') program
.description( .command('balance <address> [token_address]')
'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('Check ETH and ERC20 balance')
) .action(async (address, tokenAddress) => {
.action(async (noteString, recipient, refund) => { await init({ rpc: program.rpc })
const { currency, amount, netId, deposit } = parseNote(noteString) await printETHBalance({ address, name: '' })
await init({ rpc: program.rpc, noteNetId: netId, currency, amount }) if (tokenAddress) {
await withdraw({ await printERC20Balance({ address, name: '', tokenAddress })
deposit, }
currency, })
amount, program
recipient, .command('compliance <note>')
refund, .description(
relayerURL: program.relayer '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) => {
program const { currency, amount, netId, deposit } = parseNote(noteString)
.command('balance <address> [token_address]') await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
.description('Check ETH and ERC20 balance') const depositInfo = await loadDepositData({ deposit })
.action(async (address, tokenAddress) => { const depositDate = new Date(depositInfo.timestamp * 1000)
await init({ rpc: program.rpc }) console.log('\n=============Deposit=================')
await printETHBalance({ address, name: '' }) console.log('Deposit :', amount, currency)
if (tokenAddress) { console.log('Date :', depositDate.toLocaleDateString(), depositDate.toLocaleTimeString())
await printERC20Balance({ address, name: '', tokenAddress }) console.log('From :', `https://${getCurrentNetworkName()}etherscan.io/address/${depositInfo.from}`)
} console.log('Transaction :', `https://${getCurrentNetworkName()}etherscan.io/tx/${depositInfo.txHash}`)
}) console.log('Commitment :', depositInfo.commitment)
program if (deposit.isSpent) {
.command('compliance <note>') console.log('The note was not spent')
.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) => {
const { currency, amount, netId, deposit } = parseNote(noteString)
await init({ rpc: program.rpc, noteNetId: netId, currency, amount })
const depositInfo = await loadDepositData({ deposit })
const depositDate = new Date(depositInfo.timestamp * 1000)
console.log('\n=============Deposit=================')
console.log('Deposit :', amount, currency)
console.log('Date :', depositDate.toLocaleDateString(), depositDate.toLocaleTimeString())
console.log('From :', `https://${getCurrentNetworkName()}etherscan.io/address/${depositInfo.from}`)
console.log('Transaction :', `https://${getCurrentNetworkName()}etherscan.io/tx/${depositInfo.txHash}`)
console.log('Commitment :', depositInfo.commitment)
if (deposit.isSpent) {
console.log('The note was not spent')
}
const withdrawInfo = await loadWithdrawalData({ const withdrawInfo = await loadWithdrawalData({
amount, amount,
currency, currency,
deposit 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)
console.log('Relayer Fee :', withdrawInfo.fee, currency) console.log('Relayer Fee :', withdrawInfo.fee, currency)
console.log('Date :', withdrawalDate.toLocaleDateString(), withdrawalDate.toLocaleTimeString()) console.log('Date :', withdrawalDate.toLocaleDateString(), withdrawalDate.toLocaleTimeString())
console.log('To :', `https://${getCurrentNetworkName()}etherscan.io/address/${withdrawInfo.to}`) console.log('To :', `https://${getCurrentNetworkName()}etherscan.io/address/${withdrawInfo.to}`)
console.log('Transaction :', `https://${getCurrentNetworkName()}etherscan.io/tx/${withdrawInfo.txHash}`) console.log('Transaction :', `https://${getCurrentNetworkName()}etherscan.io/tx/${withdrawInfo.txHash}`)
console.log('Nullifier :', withdrawInfo.nullifier) console.log('Nullifier :', withdrawInfo.nullifier)
})
program
.command('test')
.description('Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.')
.action(async () => {
console.log('Start performing ETH deposit-withdraw test')
let currency = 'eth'
let amount = '0.1'
await init({ rpc: program.rpc, currency, amount })
let noteString = await deposit({ currency, amount })
let parsedNote = parseNote(noteString)
await withdraw({
deposit: parsedNote.deposit,
currency,
amount,
recipient: senderAccount,
relayerURL: program.relayer
}) })
program
.command('test')
.description('Perform an automated test. It deposits and withdraws one ETH and one ERC20 note. Uses ganache.')
.action(async () => {
console.log('Start performing ETH deposit-withdraw test')
let currency = 'eth'
let amount = '0.1'
await init({ rpc: program.rpc, currency, amount })
let noteString = await deposit({ currency, amount })
let parsedNote = parseNote(noteString)
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({ await withdraw({
deposit: parsedNote.deposit, deposit: parsedNote.deposit,
currency, currency,
amount, amount,
recipient: senderAccount, recipient: senderAccount,
refund: '0.02', refund: '0.02',
relayerURL: program.relayer relayerURL: program.relayer
})
}) })
try { })
await program.parseAsync(process.argv) try {
process.exit(0) await program.parseAsync(process.argv)
} catch (e) { process.exit(0)
console.log('Error:', e) } catch (e) {
process.exit(1) console.log('Error:', e)
} process.exit(1)
} }
} }

143
core.js Normal file

@ -0,0 +1,143 @@
const fs = require('fs')
const crypto = require('crypto')
const circomlib = require('circomlib')
const websnarkUtils = require('websnark/src/utils')
const MerkleTree = require('fixed-merkle-tree')
const circuit = require('./build/circuits/tornado.json')
const path = require('path')
const proving_key = fs.readFileSync(path.resolve(__dirname, './build/circuits/tornadoProvingKey.bin')).buffer
const buildGroth16 = require('websnark/src/groth16')
const snarkjs = require('snarkjs')
const { toBN } = require('web3-utils')
const bigInt = snarkjs.bigInt
let groth16, MERKLE_TREE_HEIGHT
/** Generate random number of specified byte length */
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes))
/** BigNumber to hex string of specified length */
function toHex(number, length = 32) {
const str = number instanceof Buffer ? number.toString('hex') : bigInt(number).toString(16)
return '0x' + str.padStart(length * 2, '0')
}
/** 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 } = {}) {
if (!nullifier && !secret) {
nullifier = rbigint(31)
secret = rbigint(31)
}
const deposit = { nullifier: bigInt(nullifier), secret: bigInt(secret) }
deposit.preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
deposit.commitment = pedersenHash(deposit.preimage)
deposit.commitmentHex = toHex(deposit.commitment)
deposit.nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
deposit.nullifierHex = toHex(deposit.nullifierHash)
return deposit
}
/**
* Generate merkle tree for a deposit.
* @param deposit Deposit object
*/
function generateMerkleProof({ deposit, events }) {
let leafIndex = -1
let argsProperty
if (events[0].returnValues) {
argsProperty = 'returnValues'
} else if (events[0].args) {
argsProperty = 'args'
} else {
throw new Error('Only implemented for web3 and ethersjs')
}
const leaves = events
.sort((a, b) => a[argsProperty].leafIndex - b[argsProperty].leafIndex) // Sort events in chronological order
.map((e) => {
const index = toBN(e[argsProperty].leafIndex).toNumber()
if (toBN(e[argsProperty].commitment).eq(toBN(deposit.commitmentHex))) {
leafIndex = index
}
return e[argsProperty].commitment.toString(10)
})
const tree = new MerkleTree(MERKLE_TREE_HEIGHT, leaves)
// Compute merkle proof of our commitment
const { pathIndices, pathElements } = tree.path(leafIndex)
return {
pathElements,
pathIndices,
root: tree.root()
}
}
/**
* Generate SNARK proof for withdrawal
* @param deposit Deposit object
* @param recipient Funds recipient
* @param relayer Relayer address
* @param fee Relayer fee
* @param refund Receive ether for exchanged tokens
*/
async function generateProof({ deposit, recipient, events, relayerAddress = 0, fee = 0, refund = 0 }) {
// Compute merkle proof of our commitment
const { root, pathElements, pathIndices } = generateMerkleProof({ deposit, events })
// Prepare circuit input
const input = {
// Public snark inputs
root: root,
nullifierHash: deposit.nullifierHash,
recipient: bigInt(recipient),
relayer: bigInt(relayerAddress),
fee: bigInt(fee),
refund: bigInt(refund),
// Private snark inputs
nullifier: deposit.nullifier,
secret: deposit.secret,
pathElements,
pathIndices
}
console.log('Generating SNARK proof')
console.time('Proof time')
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { proof } = websnarkUtils.toSolidityInput(proofData)
console.timeEnd('Proof time')
const args = [
toHex(input.root),
toHex(input.nullifierHash),
toHex(input.recipient, 20),
toHex(input.relayer, 20),
toHex(input.fee),
toHex(input.refund)
]
return { proof, args }
}
async function initialize({ merkleTreeHeight }) {
MERKLE_TREE_HEIGHT = merkleTreeHeight
groth16 = await buildGroth16()
}
module.exports = {
initialize,
createDeposit,
generateProof,
generateMerkleProof,
rbigint,
bigInt,
toHex,
pedersenHash
}

@ -1,8 +1,14 @@
{ {
"name": "cli-tornado", "name": "tornado-cli",
"version": "1.0.0", "version": "0.0.1",
"description": "", "description": "",
"main": "index.js", "main": "core.js",
"files": [
"config.js",
"core.js",
"build",
"lib"
],
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
@ -17,7 +23,8 @@
"gas-price-oracle": "^0.2.2", "gas-price-oracle": "^0.2.2",
"snarkjs": "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5", "snarkjs": "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5",
"web3": "^1.2.8", "web3": "^1.2.8",
"websnark": "git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d" "websnark": "git+https://github.com/tornadocash/websnark.git#4c0af6a8b65aabea3c09f377f63c44e7a58afa6d",
"fixed-merkle-tree": "^0.5.0"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^7.0.0" "eslint": "^7.0.0"

@ -758,6 +758,16 @@ circom@0.0.35:
typedarray-to-buffer "^3.1.5" typedarray-to-buffer "^3.1.5"
web3 "^1.2.11" web3 "^1.2.11"
"circomlib@git+https://github.com/tornadocash/circomlib.git#5beb6aee94923052faeecea40135d45b6ce6172c":
version "0.0.20"
resolved "git+https://github.com/tornadocash/circomlib.git#5beb6aee94923052faeecea40135d45b6ce6172c"
dependencies:
blake-hash "^1.1.0"
blake2b "^2.1.3"
snarkjs "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5"
typedarray-to-buffer "^3.1.5"
web3 "^1.2.11"
class-is@^1.1.0: class-is@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" resolved "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825"
@ -1638,6 +1648,14 @@ find-up@^3.0.0:
dependencies: dependencies:
locate-path "^3.0.0" locate-path "^3.0.0"
fixed-merkle-tree@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/fixed-merkle-tree/-/fixed-merkle-tree-0.5.0.tgz#401cdcf3d670c1e18bc7d3a8e81322eb1b27c1d1"
integrity sha512-egOy12EzVATX3Ru2/SLtnWprVpy/sbPCt/MbeG3ANB28jykWLEYj7EjinFnOxtsgR3gTHU6xYXX53yMn/bZqyw==
dependencies:
circomlib "git+https://github.com/tornadocash/circomlib.git#5beb6aee94923052faeecea40135d45b6ce6172c"
snarkjs "git+https://github.com/tornadocash/snarkjs.git#869181cfaf7526fe8972073d31655493a04326d5"
flat-cache@^2.0.1: flat-cache@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"