first deposit

This commit is contained in:
Alexey 2020-04-09 21:38:10 +03:00
parent 8b26474df1
commit 709bde4705
11 changed files with 354 additions and 53 deletions

View File

@ -2,7 +2,7 @@ root = true
[*] [*]
indent_style = space indent_style = space
indent_size = 2 indent_size = 4
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true

4
TODO
View File

@ -1 +1,3 @@
* update verifier * update verifier version
* shuffle outputs
*

View File

@ -29,10 +29,11 @@ template DualMux() {
// pathIndices input is an array of 0/1 selectors telling whether given pathElement is on the left or right side of merkle path // pathIndices input is an array of 0/1 selectors telling whether given pathElement is on the left or right side of merkle path
template MerkleTree(levels) { template MerkleTree(levels) {
signal input leaf; signal input leaf;
signal input root;
signal input pathElements[levels]; signal input pathElements[levels];
signal input pathIndices; signal input pathIndices;
signal output root;
component selectors[levels]; component selectors[levels];
component hashers[levels]; component hashers[levels];
@ -50,5 +51,5 @@ template MerkleTree(levels) {
hashers[i].right <== selectors[i].out[1]; hashers[i].right <== selectors[i].out[1];
} }
root === hashers[levels - 1].hash; root <== hashers[levels - 1].hash;
} }

View File

@ -38,6 +38,7 @@ template Transaction(levels, zeroLeaf) {
// data for 2 transaction outputs // data for 2 transaction outputs
signal private input outAmount[2]; signal private input outAmount[2];
signal private input outBlinding[2]; signal private input outBlinding[2];
signal private input outPubkey[2];
signal private input outPathIndices; signal private input outPathIndices;
signal private input outPathElements[levels - 1]; signal private input outPathElements[levels - 1];
@ -88,7 +89,7 @@ template Transaction(levels, zeroLeaf) {
outUtxoHasher[tx] = TransactionHasher(); outUtxoHasher[tx] = TransactionHasher();
outUtxoHasher[tx].amount <== outAmount[tx]; outUtxoHasher[tx].amount <== outAmount[tx];
outUtxoHasher[tx].blinding <== outBlinding[tx]; outUtxoHasher[tx].blinding <== outBlinding[tx];
outUtxoHasher[tx].publicKey <== keypair.publicKey; outUtxoHasher[tx].publicKey <== outPubkey[tx];
outUtxoHasher[tx].commitment === outputCommitment[tx]; outUtxoHasher[tx].commitment === outputCommitment[tx];
// Check that amount fits into 248 bits to prevent overflow // Check that amount fits into 248 bits to prevent overflow
@ -120,4 +121,4 @@ template Transaction(levels, zeroLeaf) {
} }
} }
component main = Transaction(5, 3193090221241211970002919215846211184824251841300455796635909287157453409439); component main = Transaction(5, 16923532097304556005972200564242292693309333953544141029519619077135960040221);

View File

@ -7,8 +7,11 @@ template Keypair() {
signal input privateKey; signal input privateKey;
signal output publicKey; signal output publicKey;
publicKey <== privateKey; component hasher = MiMCSponge(1, 1);
// todo hasher.ins[0] <== privateKey;
hasher.k <== 0;
publicKey <== hasher.outs[0];
} }
template TransactionHasher() { template TransactionHasher() {

View File

@ -34,8 +34,9 @@ contract TornadoPool is ReentrancyGuard {
@dev The constructor @dev The constructor
@param _verifier the address of SNARK verifier for this contract @param _verifier the address of SNARK verifier for this contract
*/ */
constructor(IVerifier _verifier) public { constructor(IVerifier _verifier, bytes32 _currentRoot) public {
verifier = _verifier; verifier = _verifier;
currentRoot = _currentRoot;
} }
function transaction( function transaction(
@ -83,8 +84,8 @@ contract TornadoPool is ReentrancyGuard {
transfer(_relayer, _fee); transfer(_relayer, _fee);
} }
emit NewCommitment(_outputCommitments[0], currentDepositIndex++); emit NewCommitment(_outputCommitments[0], currentCommitmentIndex++);
emit NewCommitment(_outputCommitments[1], currentDepositIndex++); emit NewCommitment(_outputCommitments[1], currentCommitmentIndex++);
emit NewNullifier(_inputNullifiers[0]); emit NewNullifier(_inputNullifiers[0]);
emit NewNullifier(_inputNullifiers[1]); emit NewNullifier(_inputNullifiers[1]);
// emit Transaction(); // emit Transaction();

201
lib/merkleTree.js Normal file
View File

@ -0,0 +1,201 @@
const jsStorage = require('./storage')
const hasherImpl = require('./mimc')
class MerkleTree {
constructor(n_levels, defaultElements, prefix, storage, hasher) {
this.prefix = prefix
this.storage = storage || new jsStorage()
this.hasher = hasher || new hasherImpl()
this.n_levels = n_levels
this.zero_values = []
this.totalElements = 0
let current_zero_value = '21663839004416932945382355908790599225266501822907911457504978515578255421292'
this.zero_values.push(current_zero_value)
for (let i = 0; i < n_levels; i++) {
current_zero_value = this.hasher.hash(i, current_zero_value, current_zero_value)
this.zero_values.push(
current_zero_value.toString(),
)
}
if (defaultElements) {
let level = 0
this.totalElements = defaultElements.length
defaultElements.forEach((element, i) => {
this.storage.put(MerkleTree.index_to_key(prefix, level, i), element)
})
level++
let numberOfElementsInLevel = Math.ceil(defaultElements.length / 2)
for (level; level <= this.n_levels; level++) {
for(let i = 0; i < numberOfElementsInLevel; i++) {
const leftKey = MerkleTree.index_to_key(prefix, level - 1, 2 * i)
const rightKey = MerkleTree.index_to_key(prefix, level - 1, 2 * i + 1)
const left = this.storage.get(leftKey)
const right = this.storage.get_or_element(rightKey, this.zero_values[level - 1])
const subRoot = this.hasher.hash(null, left, right)
this.storage.put(MerkleTree.index_to_key(prefix, level, i), subRoot)
}
numberOfElementsInLevel = Math.ceil(numberOfElementsInLevel / 2)
}
}
}
static index_to_key(prefix, level, index) {
const key = `${prefix}_tree_${level}_${index}`
return key
}
async root() {
let root = await this.storage.get_or_element(
MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
this.zero_values[this.n_levels],
)
return root
}
async path(index) {
class PathTraverser {
constructor(prefix, storage, zero_values) {
this.prefix = prefix
this.storage = storage
this.zero_values = zero_values
this.path_elements = []
this.path_index = []
}
async handle_index(level, element_index, sibling_index) {
const sibling = await this.storage.get_or_element(
MerkleTree.index_to_key(this.prefix, level, sibling_index),
this.zero_values[level],
)
this.path_elements.push(sibling)
this.path_index.push(element_index % 2)
}
}
index = Number(index)
let traverser = new PathTraverser(this.prefix, this.storage, this.zero_values)
const root = await this.storage.get_or_element(
MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
this.zero_values[this.n_levels],
)
const element = await this.storage.get_or_element(
MerkleTree.index_to_key(this.prefix, 0, index),
this.zero_values[0],
)
await this.traverse(index, traverser)
return {
root,
path_elements: traverser.path_elements,
path_index: traverser.path_index,
element
}
}
async update(index, element, insert = false) {
if (!insert && index >= this.totalElements) {
throw Error('Use insert method for new elements.')
} else if(insert && index < this.totalElements) {
throw Error('Use update method for existing elements.')
}
try {
class UpdateTraverser {
constructor(prefix, storage, hasher, element, zero_values) {
this.prefix = prefix
this.current_element = element
this.zero_values = zero_values
this.storage = storage
this.hasher = hasher
this.key_values_to_put = []
}
async handle_index(level, element_index, sibling_index) {
if (level == 0) {
this.original_element = await this.storage.get_or_element(
MerkleTree.index_to_key(this.prefix, level, element_index),
this.zero_values[level],
)
}
const sibling = await this.storage.get_or_element(
MerkleTree.index_to_key(this.prefix, level, sibling_index),
this.zero_values[level],
)
let left, right
if (element_index % 2 == 0) {
left = this.current_element
right = sibling
} else {
left = sibling
right = this.current_element
}
this.key_values_to_put.push({
key: MerkleTree.index_to_key(this.prefix, level, element_index),
value: this.current_element,
})
this.current_element = this.hasher.hash(level, left, right)
}
}
let traverser = new UpdateTraverser(
this.prefix,
this.storage,
this.hasher,
element,
this.zero_values
)
await this.traverse(index, traverser)
traverser.key_values_to_put.push({
key: MerkleTree.index_to_key(this.prefix, this.n_levels, 0),
value: traverser.current_element,
})
await this.storage.put_batch(traverser.key_values_to_put)
} catch(e) {
console.error(e)
}
}
async insert(element) {
const index = this.totalElements
await this.update(index, element, true)
this.totalElements++
}
// todo it can be mode optimal
async insertPair(first, second) {
await insert(first)
await insert(second)
}
async traverse(index, handler) {
let current_index = index
for (let i = 0; i < this.n_levels; i++) {
let sibling_index = current_index
if (current_index % 2 == 0) {
sibling_index += 1
} else {
sibling_index -= 1
}
await handler.handle_index(i, current_index, sibling_index)
current_index = Math.floor(current_index / 2)
}
}
getIndexByElement(element) {
for(let i = this.totalElements - 1; i >= 0; i--) {
const elementFromTree = this.storage.get(MerkleTree.index_to_key(this.prefix, 0, i))
if (elementFromTree === element) {
return i
}
}
return false
}
}
module.exports = MerkleTree

17
lib/mimc.js Normal file
View File

@ -0,0 +1,17 @@
const circomlib = require('circomlib')
const mimcsponge = circomlib.mimcsponge
const snarkjs = require('snarkjs')
const bigInt = snarkjs.bigInt
class MimcSpongeHasher {
hash(level, left, right) {
return mimcsponge.multiHash([bigInt(left), bigInt(right)]).toString()
}
hashArray(items) {
return mimcsponge.multiHash(items.map(item => bigInt(item))).toString()
}
}
module.exports = MimcSpongeHasher

39
lib/storage.js Normal file
View File

@ -0,0 +1,39 @@
class JsStorage {
constructor() {
this.db = {}
}
get(key) {
return this.db[key]
}
get_or_element(key, defaultElement) {
const element = this.db[key]
if (element === undefined) {
return defaultElement
} else {
return element
}
}
put(key, value) {
if (key === undefined || value === undefined) {
throw Error('key or value is undefined')
}
this.db[key] = value
}
del(key) {
delete this.db[key]
}
put_batch(key_values) {
key_values.forEach(element => {
this.db[element.key] = element.value
})
}
}
module.exports = JsStorage

View File

@ -1,11 +1,19 @@
/* global artifacts */ /* global artifacts */
const Verifier = artifacts.require('Verifier') const Verifier = artifacts.require('Verifier')
const TornadoPool = artifacts.require('TornadoPool') const TornadoPool = artifacts.require('TornadoPool')
const MERKLE_TREE_HEIGHT = 5
const MerkleTree = require('../lib/merkleTree')
const { bigInt } = require('snarkjs')
const toHex = (number, length = 32) => '0x' + (number instanceof Buffer ? number.toString('hex') : bigInt(number).toString(16)).padStart(length * 2, '0')
module.exports = function(deployer, network, accounts) { module.exports = function(deployer, network, accounts) {
return deployer.then(async () => { return deployer.then(async () => {
const tree = new MerkleTree(MERKLE_TREE_HEIGHT)
const root = await tree.root()
const verifier = await Verifier.deployed() const verifier = await Verifier.deployed()
const tornado = await deployer.deploy(TornadoPool, verifier.address)
const tornado = await deployer.deploy(TornadoPool, verifier.address, toHex(root))
console.log('TornadoPool\'s address ', tornado.address) console.log('TornadoPool\'s address ', tornado.address)
}) })
} }

View File

@ -1,31 +1,39 @@
const MERKLE_TREE_HEIGHT = 10; const MerkleTree = require('../lib/merkleTree')
const fs = require('fs')
const { bigInt, stringifyBigInts } = require('snarkjs')
const crypto = require('crypto')
const Hasher = require('../lib/mimc')
const Web3 = require('web3')
const buildGroth16 = require('websnark/src/groth16')
const websnarkUtils = require('websnark/src/utils')
function rbigint(bytes = 31) { let contract, web3, circuit, proving_key, groth16
const hasher = new Hasher()
} // console.log(hasher.hashArray(['21663839004416932945382355908790599225266501822907911457504978515578255421292', '21663839004416932945382355908790599225266501822907911457504978515578255421292']))
function randomBoolArray(length) { const MERKLE_TREE_HEIGHT = 5
const RPC_URL = 'http://localhost:8545'
} /** Generate random number of specified byte length */
const rbigint = (nbytes = 31) => bigInt.leBuff2int(crypto.randomBytes(nbytes))
function randomArray(length) { /** BigNumber to hex string of specified length */
const toHex = (number, length = 32) => '0x' + (number instanceof Buffer ? number.toString('hex') : bigInt(number).toString(16)).padStart(length * 2, '0')
}
function hash(dataArray) {
for(let item of dataArray) {
}
}
function merklePathIndicesToBigint(indexArray) { function merklePathIndicesToBigint(indexArray) {
let result = 0
for(let item of indexArray) {
result = (result << 1) + item
}
return result
} }
function fromPrivkey(privkey) { function fromPrivkey(privkey) {
return { return {
privkey, privkey,
pubkey: privkey, pubkey: hasher.hashArray([privkey]),
} }
} }
@ -34,7 +42,14 @@ function randomKeypair() {
} }
function createZeroUtxo(keypair) { function createZeroUtxo(keypair) {
return createUtxo(0, rbigint(), keypair.pubkey, keypair.privkey, randomBoolArray(MERKLE_TREE_HEIGHT), randomArray(MERKLE_TREE_HEIGHT)) return createUtxo(
0,
rbigint(),
keypair.pubkey,
keypair.privkey,
Array(MERKLE_TREE_HEIGHT).fill(0),
Array(MERKLE_TREE_HEIGHT).fill(0)
)
} }
function createOutput(amount, pubkey) { function createOutput(amount, pubkey) {
@ -51,9 +66,9 @@ function createInput(amount, blinding, pubkey, privkey, merklePathIndices, merkl
/// unsafe function without sanity checks /// unsafe function without sanity checks
function createUtxo(amount, blinding, pubkey, privkey, merklePathIndices, merklePathElements) { function createUtxo(amount, blinding, pubkey, privkey, merklePathIndices, merklePathElements) {
let utxo = { amount, blinding, pubkey, privkey, merklePathIndices, merklePathElements } let utxo = { amount, blinding, pubkey, privkey, merklePathIndices, merklePathElements }
utxo.commitment = hash([amount, blinding, pubkey]) utxo.commitment = hasher.hashArray([amount, blinding, pubkey])
if (privkey) { if (privkey) {
utxo.nullifier = hash([commitment, merklePathIndicesToBigint(merklePathIndices), privkey]) utxo.nullifier = hasher.hashArray([utxo.commitment, merklePathIndicesToBigint(merklePathIndices), privkey])
} }
return utxo return utxo
} }
@ -62,7 +77,7 @@ function createDeposit(amount, pubkey) {
const keypair = randomKeypair() const keypair = randomKeypair()
const tx = { const tx = {
inputs: [createZeroUtxo(keypair), createZeroUtxo(keypair)], inputs: [createZeroUtxo(keypair), createZeroUtxo(keypair)],
outputs: [createOutput(amount, pubkey), createZeroUtxo()], // todo shuffle outputs: [createOutput(amount, pubkey), createZeroUtxo(keypair)], // todo shuffle
} }
return tx; return tx;
} }
@ -73,28 +88,38 @@ async function buildMerkleTree() {
const leaves = events const leaves = events
.sort((a, b) => a.returnValues.index - b.returnValues.index) // todo sort by event date .sort((a, b) => a.returnValues.index - b.returnValues.index) // todo sort by event date
.map(e => e.returnValues.commitment) .map(e => e.returnValues.commitment)
return new merkleTree(MERKLE_TREE_HEIGHT, leaves) return new MerkleTree(MERKLE_TREE_HEIGHT, leaves)
} }
function insertTransaction(tree, tx) { async function insertOutput(tree, output) {
let path = await tree.insertPair(tx.outputs[0].commitment, tx.outputs[1].commitment) await tree.insert(output.commitment)
let { path_elements, path_index } = await tree.path(tree.totalElements - 1)
tx.outputs[0].merklePath = [...path, 0] output.merklePathIndices = path_index
tx.outputs[1].merklePath = [...path, 1] output.merklePathElements = path_elements
} }
async function main() { async function main() {
const amount = 1e6; web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL, { timeout: 5 * 60 * 1000 }), null, { transactionConfirmationBlocks: 1 })
circuit = require('../build/circuits/transaction.json')
proving_key = fs.readFileSync('../build/circuits/transaction_proving_key.bin').buffer
groth16 = await buildGroth16()
netId = await web3.eth.net.getId()
const contractData = require('../build/contracts/TornadoPool.json')
contract = new web3.eth.Contract(contractData.abi, contractData.networks[netId].address)
web3.eth.defaultAccount = (await web3.eth.getAccounts())[0]
const amount = 1e6;
const tree = await buildMerkleTree() const tree = await buildMerkleTree()
const oldRoot = tree.root; const oldRoot = await tree.root()
const keypair = randomKeypair() const keypair = randomKeypair()
const tx = createDeposit(amount, keypair.pubkey) const tx = createDeposit(amount, keypair.pubkey)
insertTransaction(tree, tx) await insertOutput(tree, tx.outputs[0])
await insertOutput(tree, tx.outputs[1])
console.log('Note', tx.outputs[0])
let input = { let input = {
root: oldRoot, root: oldRoot,
newRoot: tree.root, newRoot: await tree.root(),
inputNullifier: [tx.inputs[0].nullifier, tx.inputs[1].nullifier], inputNullifier: [tx.inputs[0].nullifier, tx.inputs[1].nullifier],
outputCommitment: [tx.outputs[0].commitment, tx.outputs[1].commitment], outputCommitment: [tx.outputs[0].commitment, tx.outputs[1].commitment],
extAmount: amount, extAmount: amount,
@ -114,10 +139,13 @@ async function main() {
// data for 2 transaction outputs // data for 2 transaction outputs
outAmount: [tx.outputs[0].amount, tx.outputs[1].amount], outAmount: [tx.outputs[0].amount, tx.outputs[1].amount],
outBlinding: [tx.outputs[0].blinding, tx.outputs[1].blinding], outBlinding: [tx.outputs[0].blinding, tx.outputs[1].blinding],
outPathIndices: merklePathIndicesToBigint(tx.outputs[0].merklePathIndices.slice(0, -1)), outPubkey: [tx.outputs[0].pubkey, tx.outputs[1].pubkey],
outPathElements: tx.outputs[0].merklePathElements.slice(0, -1) outPathIndices: merklePathIndicesToBigint(tx.outputs[0].merklePathIndices.slice(1)),
outPathElements: tx.outputs[0].merklePathElements.slice(1)
} }
console.log('input', JSON.stringify(stringifyBigInts(input)))
console.log('Generating SNARK proof...') console.log('Generating SNARK proof...')
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key) const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
const { proof } = websnarkUtils.toSolidityInput(proofData) const { proof } = websnarkUtils.toSolidityInput(proofData)
@ -125,17 +153,17 @@ async function main() {
const args = [ const args = [
toHex(input.root), toHex(input.root),
toHex(input.newRoot), toHex(input.newRoot),
[toHex(input.inputs[0].nullifier), toHex(input.inputs[1].nullifier)], [toHex(tx.inputs[0].nullifier), toHex(tx.inputs[1].nullifier)],
[toHex(input.inputs[0].commitment), toHex(input.inputs[1].commitment)], [toHex(tx.outputs[0].commitment), toHex(tx.outputs[1].commitment)],
toHex(input.amount), toHex(amount),
toHex(input.fee), toHex(input.fee),
toHex(input.recipient, 20), toHex(input.recipient, 20),
toHex(input.relayer, 20), toHex(input.relayer, 20),
] ]
console.log('Sending withdrawal transaction...') console.log('Sending deposit transaction...')
const receipt = await contract.methods.transaction(proof, ...args).send({ valued: amount, from: web3.eth.defaultAccount, gas: 1e6 }) const receipt = await contract.methods.transaction(proof, ...args).send({ value: amount, from: web3.eth.defaultAccount, gas: 1e6 })
console.log(`https://kovan.etherscan.io/tx/${receipt.transactionHash}`) console.log(`Receipt ${receipt.transactionHash}`)
} }