2020-04-09 22:08:16 +02:00
|
|
|
/* eslint-disable no-console */
|
2021-06-06 19:31:32 +02:00
|
|
|
const MerkleTree = require('fixed-merkle-tree')
|
|
|
|
const { ethers } = require('hardhat')
|
2021-06-16 01:50:06 +02:00
|
|
|
const { BigNumber } = ethers
|
2021-06-16 14:24:00 +02:00
|
|
|
const { toFixedHex, poseidonHash2, getExtDataHash, FIELD_SIZE, shuffle } = require('./utils')
|
2021-06-08 20:50:34 +02:00
|
|
|
const Utxo = require('./utxo')
|
2020-04-09 12:36:45 +02:00
|
|
|
|
2021-06-06 19:31:32 +02:00
|
|
|
const { prove } = require('./prover')
|
2020-04-09 20:38:10 +02:00
|
|
|
const MERKLE_TREE_HEIGHT = 5
|
2020-04-09 12:36:45 +02:00
|
|
|
|
2021-06-09 12:56:33 +02:00
|
|
|
async function buildMerkleTree({ tornadoPool }) {
|
|
|
|
const filter = tornadoPool.filters.NewCommitment()
|
|
|
|
const events = await tornadoPool.queryFilter(filter, 0)
|
|
|
|
|
2021-06-16 14:24:00 +02:00
|
|
|
const leaves = events.sort((a, b) => a.args.index - b.args.index).map((e) => toFixedHex(e.args.commitment))
|
2021-06-06 19:31:32 +02:00
|
|
|
return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 })
|
2020-04-09 12:36:45 +02:00
|
|
|
}
|
|
|
|
|
2022-01-22 01:23:33 +01:00
|
|
|
async function getProof({
|
|
|
|
inputs,
|
|
|
|
outputs,
|
|
|
|
tree,
|
|
|
|
extAmount,
|
|
|
|
fee,
|
|
|
|
recipient,
|
|
|
|
relayer,
|
|
|
|
isL1Withdrawal,
|
|
|
|
l1Fee,
|
|
|
|
}) {
|
2021-06-16 14:24:00 +02:00
|
|
|
inputs = shuffle(inputs)
|
|
|
|
outputs = shuffle(outputs)
|
2021-06-07 12:12:15 +02:00
|
|
|
|
2021-06-08 20:50:34 +02:00
|
|
|
let inputMerklePathIndices = []
|
|
|
|
let inputMerklePathElements = []
|
|
|
|
|
|
|
|
for (const input of inputs) {
|
|
|
|
if (input.amount > 0) {
|
2021-10-26 17:35:26 +02:00
|
|
|
input.index = tree.indexOf(toFixedHex(input.getCommitment()))
|
|
|
|
if (input.index < 0) {
|
2021-06-15 22:02:59 +02:00
|
|
|
throw new Error(`Input commitment ${toFixedHex(input.getCommitment())} was not found`)
|
2021-06-08 20:50:34 +02:00
|
|
|
}
|
2021-10-26 17:35:26 +02:00
|
|
|
inputMerklePathIndices.push(input.index)
|
|
|
|
inputMerklePathElements.push(tree.path(input.index).pathElements)
|
2021-06-08 20:50:34 +02:00
|
|
|
} else {
|
|
|
|
inputMerklePathIndices.push(0)
|
|
|
|
inputMerklePathElements.push(new Array(tree.levels).fill(0))
|
|
|
|
}
|
2021-06-07 12:12:15 +02:00
|
|
|
}
|
2020-04-09 21:12:45 +02:00
|
|
|
|
2021-06-08 20:50:34 +02:00
|
|
|
const extData = {
|
|
|
|
recipient: toFixedHex(recipient, 20),
|
2021-08-13 19:07:53 +02:00
|
|
|
extAmount: toFixedHex(extAmount),
|
2021-06-08 20:50:34 +02:00
|
|
|
relayer: toFixedHex(relayer, 20),
|
2021-08-13 19:07:53 +02:00
|
|
|
fee: toFixedHex(fee),
|
2021-06-16 01:50:06 +02:00
|
|
|
encryptedOutput1: outputs[0].encrypt(),
|
|
|
|
encryptedOutput2: outputs[1].encrypt(),
|
2021-09-29 21:39:30 +02:00
|
|
|
isL1Withdrawal,
|
2022-01-22 01:21:34 +01:00
|
|
|
l1Fee,
|
2021-06-08 20:50:34 +02:00
|
|
|
}
|
2021-06-07 12:12:15 +02:00
|
|
|
|
2021-06-08 20:50:34 +02:00
|
|
|
const extDataHash = getExtDataHash(extData)
|
2020-04-09 21:12:45 +02:00
|
|
|
let input = {
|
2021-10-26 17:35:26 +02:00
|
|
|
root: tree.root(),
|
2021-06-09 12:56:33 +02:00
|
|
|
inputNullifier: inputs.map((x) => x.getNullifier()),
|
|
|
|
outputCommitment: outputs.map((x) => x.getCommitment()),
|
2021-08-16 21:17:07 +02:00
|
|
|
publicAmount: BigNumber.from(extAmount).sub(fee).add(FIELD_SIZE).mod(FIELD_SIZE).toString(),
|
2021-06-08 20:50:34 +02:00
|
|
|
extDataHash,
|
2020-04-09 21:12:45 +02:00
|
|
|
|
|
|
|
// data for 2 transaction inputs
|
2021-06-09 12:56:33 +02:00
|
|
|
inAmount: inputs.map((x) => x.amount),
|
2021-06-09 13:19:22 +02:00
|
|
|
inPrivateKey: inputs.map((x) => x.keypair.privkey),
|
2021-06-09 12:56:33 +02:00
|
|
|
inBlinding: inputs.map((x) => x.blinding),
|
2021-06-08 20:50:34 +02:00
|
|
|
inPathIndices: inputMerklePathIndices,
|
|
|
|
inPathElements: inputMerklePathElements,
|
2020-04-09 21:12:45 +02:00
|
|
|
|
|
|
|
// data for 2 transaction outputs
|
2021-06-09 12:56:33 +02:00
|
|
|
outAmount: outputs.map((x) => x.amount),
|
|
|
|
outBlinding: outputs.map((x) => x.blinding),
|
2021-06-09 13:19:22 +02:00
|
|
|
outPubkey: outputs.map((x) => x.keypair.pubkey),
|
2020-04-09 21:12:45 +02:00
|
|
|
}
|
|
|
|
|
2021-06-09 12:58:57 +02:00
|
|
|
const proof = await prove(input, `./artifacts/circuits/transaction${inputs.length}`)
|
2020-04-09 21:12:45 +02:00
|
|
|
|
2021-07-22 16:01:22 +02:00
|
|
|
const args = {
|
|
|
|
proof,
|
|
|
|
root: toFixedHex(input.root),
|
|
|
|
inputNullifiers: inputs.map((x) => toFixedHex(x.getNullifier())),
|
|
|
|
outputCommitments: outputs.map((x) => toFixedHex(x.getCommitment())),
|
2021-08-13 19:07:53 +02:00
|
|
|
publicAmount: toFixedHex(input.publicAmount),
|
2021-07-22 16:01:22 +02:00
|
|
|
extDataHash: toFixedHex(extDataHash),
|
|
|
|
}
|
2021-06-08 20:50:34 +02:00
|
|
|
// console.log('Solidity args', args)
|
2020-04-09 21:12:45 +02:00
|
|
|
|
2021-06-07 12:12:15 +02:00
|
|
|
return {
|
2021-07-22 16:01:22 +02:00
|
|
|
extData,
|
2021-06-07 12:12:15 +02:00
|
|
|
args,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-22 16:01:22 +02:00
|
|
|
async function prepareTransaction({
|
|
|
|
tornadoPool,
|
|
|
|
inputs = [],
|
|
|
|
outputs = [],
|
|
|
|
fee = 0,
|
|
|
|
recipient = 0,
|
|
|
|
relayer = 0,
|
2021-09-29 21:39:30 +02:00
|
|
|
isL1Withdrawal = false,
|
2022-01-22 01:21:34 +01:00
|
|
|
l1Fee = 0,
|
2021-07-22 16:01:22 +02:00
|
|
|
}) {
|
2021-06-16 01:50:06 +02:00
|
|
|
if (inputs.length > 16 || outputs.length > 2) {
|
|
|
|
throw new Error('Incorrect inputs/outputs count')
|
|
|
|
}
|
2021-06-16 02:31:31 +02:00
|
|
|
while (inputs.length !== 2 && inputs.length < 16) {
|
2021-06-16 01:50:06 +02:00
|
|
|
inputs.push(new Utxo())
|
|
|
|
}
|
2021-06-16 02:31:31 +02:00
|
|
|
while (outputs.length < 2) {
|
2021-06-16 01:50:06 +02:00
|
|
|
outputs.push(new Utxo())
|
|
|
|
}
|
2020-04-09 21:12:45 +02:00
|
|
|
|
2021-06-16 01:50:06 +02:00
|
|
|
let extAmount = BigNumber.from(fee)
|
|
|
|
.add(outputs.reduce((sum, x) => sum.add(x.amount), BigNumber.from(0)))
|
|
|
|
.sub(inputs.reduce((sum, x) => sum.add(x.amount), BigNumber.from(0)))
|
2021-06-16 10:28:39 +02:00
|
|
|
|
2021-07-22 16:01:22 +02:00
|
|
|
const { args, extData } = await getProof({
|
2021-06-15 13:47:54 +02:00
|
|
|
inputs,
|
|
|
|
outputs,
|
|
|
|
tree: await buildMerkleTree({ tornadoPool }),
|
2021-06-16 01:50:06 +02:00
|
|
|
extAmount,
|
|
|
|
fee,
|
|
|
|
recipient,
|
|
|
|
relayer,
|
2021-09-29 21:39:30 +02:00
|
|
|
isL1Withdrawal,
|
2022-01-22 01:21:34 +01:00
|
|
|
l1Fee,
|
2021-06-15 13:47:54 +02:00
|
|
|
})
|
|
|
|
|
2021-07-22 16:01:22 +02:00
|
|
|
return {
|
|
|
|
args,
|
|
|
|
extData,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function transaction({ tornadoPool, ...rest }) {
|
2021-09-29 21:39:30 +02:00
|
|
|
const { args, extData } = await prepareTransaction({
|
2021-07-22 16:01:22 +02:00
|
|
|
tornadoPool,
|
|
|
|
...rest,
|
|
|
|
})
|
|
|
|
|
2021-09-29 21:39:30 +02:00
|
|
|
const receipt = await tornadoPool.transact(args, extData, {
|
2021-10-06 16:51:46 +02:00
|
|
|
gasLimit: 2e6,
|
2021-06-15 13:47:54 +02:00
|
|
|
})
|
2021-09-30 17:34:07 +02:00
|
|
|
return await receipt.wait()
|
2020-04-09 12:36:45 +02:00
|
|
|
}
|
|
|
|
|
2021-10-04 18:28:08 +02:00
|
|
|
async function registerAndTransact({ tornadoPool, account, ...rest }) {
|
2021-09-29 21:39:30 +02:00
|
|
|
const { args, extData } = await prepareTransaction({
|
2021-07-22 16:01:22 +02:00
|
|
|
tornadoPool,
|
|
|
|
...rest,
|
|
|
|
})
|
|
|
|
|
2021-10-04 18:28:08 +02:00
|
|
|
const receipt = await tornadoPool.registerAndTransact(account, args, extData, {
|
2021-07-22 16:01:22 +02:00
|
|
|
gasLimit: 2e6,
|
|
|
|
})
|
|
|
|
await receipt.wait()
|
|
|
|
}
|
|
|
|
|
2021-11-01 18:56:24 +01:00
|
|
|
module.exports = { transaction, registerAndTransact, prepareTransaction, buildMerkleTree }
|