mirror of
https://github.com/tornadocash/tornado-nova
synced 2024-02-02 14:53:56 +01:00
ethers; tests
This commit is contained in:
parent
01c4930dcd
commit
cd18bef60b
14
TODO
14
TODO
@ -1,11 +1,9 @@
|
||||
* shuffle outputs and inputs
|
||||
* utxo data encryption for recipient as for mining
|
||||
- combine privkey hash and ethereum public key = address
|
||||
* outputs merging
|
||||
* switch web3 to etherjs
|
||||
* tests
|
||||
* wasmsnark
|
||||
* ERC20?
|
||||
* design
|
||||
* outputs merging (second snark with 32 inputs) (poma)
|
||||
* wasmsnark (poma)
|
||||
* relayer
|
||||
* race condition ?
|
||||
|
||||
* design
|
||||
* race condition ? (sequencer or something)
|
||||
* the current trusted setup is not secure
|
||||
|
@ -21,6 +21,7 @@
|
||||
"@nomiclabs/hardhat-waffle": "^2.0.1",
|
||||
"@openzeppelin/contracts": "^3.4.0",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"chai": "^4.3.4",
|
||||
"circom": "^0.5.45",
|
||||
"circom_runtime": "^0.1.13",
|
||||
"circomlib": "git+https://github.com/tornadocash/circomlib.git#d20d53411d1bef61f38c99a8b36d5d0cc4836aa1",
|
||||
|
92
src/index.js
92
src/index.js
@ -1,29 +1,27 @@
|
||||
/* eslint-disable no-console */
|
||||
const MerkleTree = require('fixed-merkle-tree')
|
||||
const Web3 = require('web3')
|
||||
const { ethers } = require('hardhat')
|
||||
const { BigNumber } = ethers
|
||||
const { toFixedHex, poseidonHash2, getExtDataHash, FIELD_SIZE } = require('./utils')
|
||||
const Utxo = require('./utxo')
|
||||
|
||||
let contract, web3
|
||||
const { prove } = require('./prover')
|
||||
const MERKLE_TREE_HEIGHT = 5
|
||||
const RPC_URL = 'http://localhost:8545'
|
||||
|
||||
async function buildMerkleTree() {
|
||||
async function buildMerkleTree({ tornadoPool }) {
|
||||
console.log('Getting contract state...')
|
||||
const events = await contract.getPastEvents('NewCommitment', { fromBlock: 0, toBlock: 'latest' })
|
||||
const filter = tornadoPool.filters.NewCommitment()
|
||||
const events = await tornadoPool.queryFilter(filter, 0)
|
||||
|
||||
const leaves = events
|
||||
.sort((a, b) => a.returnValues.index - b.returnValues.index) // todo sort by event date
|
||||
.map((e) => toFixedHex(e.returnValues.commitment))
|
||||
.sort((a, b) => a.args.index - b.args.index) // todo sort by event date
|
||||
.map((e) => toFixedHex(e.args.commitment))
|
||||
// console.log('leaves', leaves)
|
||||
return new MerkleTree(MERKLE_TREE_HEIGHT, leaves, { hashFunction: poseidonHash2 })
|
||||
}
|
||||
|
||||
async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, relayer }) {
|
||||
// todo shuffle inputs and outputs
|
||||
if (inputs.length !== 2 || outputs.length !== 2 ) {
|
||||
if (inputs.length !== 2 || outputs.length !== 2) {
|
||||
throw new Error('Unsupported number of inputs/outputs')
|
||||
}
|
||||
|
||||
@ -63,23 +61,23 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
|
||||
let input = {
|
||||
root: oldRoot,
|
||||
newRoot: tree.root(),
|
||||
inputNullifier: inputs.map(x => x.getNullifier()),
|
||||
outputCommitment: outputs.map(x => x.getCommitment()),
|
||||
inputNullifier: inputs.map((x) => x.getNullifier()),
|
||||
outputCommitment: outputs.map((x) => x.getCommitment()),
|
||||
extAmount,
|
||||
fee,
|
||||
extDataHash,
|
||||
|
||||
// data for 2 transaction inputs
|
||||
inAmount: inputs.map(x => x.amount),
|
||||
inPrivateKey: inputs.map(x => x.privkey),
|
||||
inBlinding: inputs.map(x => x.blinding),
|
||||
inAmount: inputs.map((x) => x.amount),
|
||||
inPrivateKey: inputs.map((x) => x.privkey),
|
||||
inBlinding: inputs.map((x) => x.blinding),
|
||||
inPathIndices: inputMerklePathIndices,
|
||||
inPathElements: inputMerklePathElements,
|
||||
|
||||
// data for 2 transaction outputs
|
||||
outAmount: outputs.map(x => x.amount),
|
||||
outBlinding: outputs.map(x => x.blinding),
|
||||
outPubkey: outputs.map(x => x.pubkey),
|
||||
outAmount: outputs.map((x) => x.amount),
|
||||
outBlinding: outputs.map((x) => x.blinding),
|
||||
outPubkey: outputs.map((x) => x.pubkey),
|
||||
outPathIndices: outputIndex >> 1,
|
||||
outPathElements: outputPath,
|
||||
}
|
||||
@ -92,8 +90,8 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
toFixedHex(input.newRoot),
|
||||
inputs.map(x => toFixedHex(x.getNullifier())),
|
||||
outputs.map(x => toFixedHex(x.getCommitment())),
|
||||
inputs.map((x) => toFixedHex(x.getNullifier())),
|
||||
outputs.map((x) => toFixedHex(x.getCommitment())),
|
||||
toFixedHex(extAmount),
|
||||
toFixedHex(fee),
|
||||
extData,
|
||||
@ -107,7 +105,7 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
|
||||
}
|
||||
}
|
||||
|
||||
async function deposit() {
|
||||
async function deposit({ tornadoPool }) {
|
||||
const amount = 1e6
|
||||
const inputs = [new Utxo(), new Utxo()]
|
||||
const outputs = [new Utxo({ amount }), new Utxo()]
|
||||
@ -115,7 +113,7 @@ async function deposit() {
|
||||
const { proof, args } = await getProof({
|
||||
inputs,
|
||||
outputs,
|
||||
tree: await buildMerkleTree(),
|
||||
tree: await buildMerkleTree({ tornadoPool }),
|
||||
extAmount: amount,
|
||||
fee: 0,
|
||||
recipient: 0,
|
||||
@ -123,24 +121,25 @@ async function deposit() {
|
||||
})
|
||||
|
||||
console.log('Sending deposit transaction...')
|
||||
const receipt = await contract.methods
|
||||
.transaction(proof, ...args)
|
||||
.send({ value: amount, from: web3.eth.defaultAccount, gas: 1e6 })
|
||||
console.log(`Receipt ${receipt.transactionHash}`)
|
||||
const receipt = await tornadoPool.transaction(proof, ...args, {
|
||||
value: amount,
|
||||
gasLimit: 1e6,
|
||||
})
|
||||
console.log(`Receipt ${receipt.hash}`)
|
||||
return outputs[0]
|
||||
}
|
||||
|
||||
async function transact(utxo) {
|
||||
async function transact({ tornadoPool, utxo }) {
|
||||
const inputs = [utxo, new Utxo()]
|
||||
const outputs = [
|
||||
new Utxo({ amount: utxo.amount / 4 }),
|
||||
new Utxo({ amount: utxo.amount * 3 / 4, privkey: utxo.privkey}),
|
||||
new Utxo({ amount: (utxo.amount * 3) / 4, privkey: utxo.privkey }),
|
||||
]
|
||||
|
||||
const { proof, args } = await getProof({
|
||||
inputs,
|
||||
outputs,
|
||||
tree: await buildMerkleTree(),
|
||||
tree: await buildMerkleTree({ tornadoPool }),
|
||||
extAmount: 0,
|
||||
fee: 0,
|
||||
recipient: 0,
|
||||
@ -148,49 +147,28 @@ async function transact(utxo) {
|
||||
})
|
||||
|
||||
console.log('Sending transfer transaction...')
|
||||
const receipt = await contract.methods
|
||||
.transaction(proof, ...args)
|
||||
.send({ from: web3.eth.defaultAccount, gas: 1e6 })
|
||||
console.log(`Receipt ${receipt.transactionHash}`)
|
||||
const receipt = await tornadoPool.transaction(proof, ...args, { gasLimit: 1e6 })
|
||||
console.log(`Receipt ${receipt.hash}`)
|
||||
return outputs[0]
|
||||
}
|
||||
|
||||
async function withdraw(utxo) {
|
||||
async function withdraw({ tornadoPool, utxo, recipient }) {
|
||||
const inputs = [utxo, new Utxo()]
|
||||
const outputs = [new Utxo(), new Utxo()]
|
||||
|
||||
const { proof, args } = await getProof({
|
||||
inputs,
|
||||
outputs,
|
||||
tree: await buildMerkleTree(),
|
||||
tree: await buildMerkleTree({ tornadoPool }),
|
||||
extAmount: FIELD_SIZE.sub(utxo.amount),
|
||||
fee: 0,
|
||||
recipient: '0xc2Ba33d4c0d2A92fb4f1a07C273c5d21E688Eb48',
|
||||
recipient,
|
||||
relayer: 0,
|
||||
})
|
||||
|
||||
console.log('Sending withdraw transaction...')
|
||||
const receipt = await contract.methods
|
||||
.transaction(proof, ...args)
|
||||
.send({ from: web3.eth.defaultAccount, gas: 1e6 })
|
||||
console.log(`Receipt ${receipt.transactionHash}`)
|
||||
|
||||
let bal = await web3.eth.getBalance('0xc2Ba33d4c0d2A92fb4f1a07C273c5d21E688Eb48')
|
||||
console.log('balance', bal)
|
||||
const receipt = await tornadoPool.transaction(proof, ...args, { gasLimit: 1e6 })
|
||||
console.log(`Receipt ${receipt.hash}`)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL, { timeout: 5 * 60 * 1000 }), null, {
|
||||
transactionConfirmationBlocks: 1,
|
||||
})
|
||||
netId = await web3.eth.net.getId()
|
||||
const contractData = require('../artifacts/contracts/TornadoPool.sol/TornadoPool.json')
|
||||
contract = new web3.eth.Contract(contractData.abi, '0x0E801D84Fa97b50751Dbf25036d067dCf18858bF')
|
||||
web3.eth.defaultAccount = (await web3.eth.getAccounts())[0]
|
||||
|
||||
const utxo1 = await deposit()
|
||||
const utxo2 = await transact(utxo1)
|
||||
await withdraw(utxo2)
|
||||
}
|
||||
|
||||
main()
|
||||
module.exports = { deposit, withdraw, transact }
|
||||
|
22
src/utils.js
22
src/utils.js
@ -1,7 +1,7 @@
|
||||
const crypto = require('crypto')
|
||||
const ethers = require('ethers')
|
||||
const { ethers } = require('hardhat')
|
||||
const BigNumber = ethers.BigNumber
|
||||
const {poseidon} = require('circomlib')
|
||||
const { poseidon } = require('circomlib')
|
||||
|
||||
const poseidonHash = (items) => BigNumber.from(poseidon(items).toString())
|
||||
const poseidonHash2 = (a, b) => poseidonHash([a, b])
|
||||
@ -12,17 +12,19 @@ const FIELD_SIZE = BigNumber.from(
|
||||
/** Generate random number of specified byte length */
|
||||
const randomBN = (nbytes = 31) => BigNumber.from(crypto.randomBytes(nbytes))
|
||||
|
||||
function getExtDataHash({recipient, relayer, encryptedOutput1, encryptedOutput2}) {
|
||||
function getExtDataHash({ recipient, relayer, encryptedOutput1, encryptedOutput2 }) {
|
||||
const abi = new ethers.utils.AbiCoder()
|
||||
|
||||
const encodedData = abi.encode(
|
||||
['tuple(address recipient,address relayer,bytes encryptedOutput1,bytes encryptedOutput2)'],
|
||||
[{
|
||||
[
|
||||
{
|
||||
recipient: toFixedHex(recipient, 20),
|
||||
relayer: toFixedHex(relayer, 20),
|
||||
encryptedOutput1: encryptedOutput1,
|
||||
encryptedOutput2: encryptedOutput2,
|
||||
}],
|
||||
},
|
||||
],
|
||||
)
|
||||
const hash = ethers.utils.keccak256(encodedData)
|
||||
return BigNumber.from(hash).mod(FIELD_SIZE)
|
||||
@ -45,6 +47,14 @@ const toBuffer = (value, length) =>
|
||||
'hex',
|
||||
)
|
||||
|
||||
async function takeSnapshot() {
|
||||
return await ethers.provider.send('evm_snapshot', [])
|
||||
}
|
||||
|
||||
async function revertSnapshot(id) {
|
||||
await ethers.provider.send('evm_revert', [id])
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FIELD_SIZE,
|
||||
randomBN,
|
||||
@ -53,4 +63,6 @@ module.exports = {
|
||||
poseidonHash,
|
||||
poseidonHash2,
|
||||
getExtDataHash,
|
||||
takeSnapshot,
|
||||
revertSnapshot,
|
||||
}
|
||||
|
14
src/utxo.js
14
src/utxo.js
@ -10,19 +10,19 @@ function fromPrivkey(privkey) {
|
||||
}
|
||||
|
||||
class Utxo {
|
||||
constructor({amount, pubkey, privkey, blinding, index} = {}) {
|
||||
constructor({ amount, pubkey, privkey, blinding, index } = {}) {
|
||||
if (!pubkey) {
|
||||
if (privkey) {
|
||||
pubkey = fromPrivkey(privkey).pubkey
|
||||
} else {
|
||||
({pubkey, privkey} = fromPrivkey(randomBN()))
|
||||
;({ pubkey, privkey } = fromPrivkey(randomBN()))
|
||||
}
|
||||
}
|
||||
this.amount = BigNumber.from(amount || 0);
|
||||
this.blinding = blinding || randomBN();
|
||||
this.pubkey = pubkey;
|
||||
this.privkey = privkey;
|
||||
this.index = index;
|
||||
this.amount = BigNumber.from(amount || 0)
|
||||
this.blinding = blinding || randomBN()
|
||||
this.pubkey = pubkey
|
||||
this.privkey = privkey
|
||||
this.index = index
|
||||
}
|
||||
|
||||
getCommitment() {
|
||||
|
45
test/full.test.js
Normal file
45
test/full.test.js
Normal file
@ -0,0 +1,45 @@
|
||||
/* global ethers */
|
||||
const { expect, should } = require('chai')
|
||||
should()
|
||||
|
||||
const { poseidonHash2, toFixedHex, takeSnapshot, revertSnapshot } = require('../src/utils')
|
||||
|
||||
const MERKLE_TREE_HEIGHT = 5
|
||||
const MerkleTree = require('fixed-merkle-tree')
|
||||
|
||||
const { deposit, transact, withdraw } = require('../src/index')
|
||||
|
||||
describe('TornadoPool', () => {
|
||||
let snapshotId, tornadoPool
|
||||
|
||||
/* prettier-ignore */
|
||||
before(async function () {
|
||||
const Verifier = await ethers.getContractFactory('Verifier')
|
||||
const verifier = await Verifier.deploy()
|
||||
await verifier.deployed()
|
||||
|
||||
const tree = new MerkleTree(MERKLE_TREE_HEIGHT, [], { hashFunction: poseidonHash2 })
|
||||
const root = await tree.root()
|
||||
|
||||
const Pool = await ethers.getContractFactory('TornadoPool')
|
||||
tornadoPool = await Pool.deploy(verifier.address, toFixedHex(root))
|
||||
|
||||
snapshotId = await takeSnapshot()
|
||||
})
|
||||
|
||||
it('should deposit, transact and withdraw', async function () {
|
||||
const utxo1 = await deposit({ tornadoPool })
|
||||
const utxo2 = await transact({ tornadoPool, utxo: utxo1 })
|
||||
|
||||
const recipient = '0xc2Ba33d4c0d2A92fb4f1a07C273c5d21E688Eb48'
|
||||
await withdraw({ tornadoPool, utxo: utxo2, recipient })
|
||||
|
||||
let bal = await ethers.provider.getBalance(recipient)
|
||||
expect(bal).to.be.gt(0)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await revertSnapshot(snapshotId)
|
||||
snapshotId = await takeSnapshot()
|
||||
})
|
||||
})
|
@ -1773,9 +1773,9 @@ blake2b-wasm@^1.1.0:
|
||||
dependencies:
|
||||
nanoassert "^1.0.0"
|
||||
|
||||
"blake2b-wasm@git+https://github.com/jbaylina/blake2b-wasm.git":
|
||||
"blake2b-wasm@https://github.com/jbaylina/blake2b-wasm.git":
|
||||
version "2.1.0"
|
||||
resolved "git+https://github.com/jbaylina/blake2b-wasm.git#0d5f024b212429c7f50a7f533aa3a2406b5b42b3"
|
||||
resolved "https://github.com/jbaylina/blake2b-wasm.git#0d5f024b212429c7f50a7f533aa3a2406b5b42b3"
|
||||
dependencies:
|
||||
nanoassert "^1.0.0"
|
||||
|
||||
@ -2078,7 +2078,7 @@ caseless@~0.12.0:
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
chai@^4.2.0:
|
||||
chai@^4.2.0, chai@^4.3.4:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.4.tgz#b55e655b31e1eac7099be4c08c21964fce2e6c49"
|
||||
integrity sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==
|
||||
|
Loading…
Reference in New Issue
Block a user