tornado-cli/core.js

144 lines
4.2 KiB
JavaScript

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
}