144 lines
4.2 KiB
JavaScript
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
|
||
|
}
|