mirror of
https://github.com/tornadocash/tornado-nova
synced 2024-02-02 14:53:56 +01:00
updates
This commit is contained in:
parent
516d599502
commit
238431233b
15
TODO
15
TODO
@ -1,4 +1,11 @@
|
|||||||
* update verifier version
|
* shuffle outputs and inputs
|
||||||
* shuffle outputs
|
* utxo data encryption for recipient as for mining
|
||||||
* utxo data encryption for recipient
|
- combine privkey hash and ethereum public key = address
|
||||||
* poseidon
|
* outputs merging
|
||||||
|
* switch web3 to etherjs
|
||||||
|
* tests
|
||||||
|
* wasmsnark
|
||||||
|
* ERC20?
|
||||||
|
* design
|
||||||
|
* relayer
|
||||||
|
* race condition ?
|
||||||
|
@ -24,8 +24,7 @@ template Transaction(levels, zeroLeaf) {
|
|||||||
// correct extAmount range is enforced on the smart contract
|
// correct extAmount range is enforced on the smart contract
|
||||||
signal input extAmount;
|
signal input extAmount;
|
||||||
signal input fee;
|
signal input fee;
|
||||||
signal input recipient;
|
signal input extDataHash;
|
||||||
signal input relayer;
|
|
||||||
|
|
||||||
signal private input privateKey;
|
signal private input privateKey;
|
||||||
|
|
||||||
@ -121,4 +120,6 @@ template Transaction(levels, zeroLeaf) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// zeroLeaf = Poseidon(zero, zero)
|
||||||
|
// default `zero` value is keccak256("tornado") % FIELD_SIZE = 21663839004416932945382355908790599225266501822907911457504978515578255421292
|
||||||
component main = Transaction(5, 11850551329423159860688778991827824730037759162201783566284850822760196767874);
|
component main = Transaction(5, 11850551329423159860688778991827824730037759162201783566284850822760196767874);
|
||||||
|
@ -11,11 +11,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
pragma solidity ^0.6.0;
|
pragma solidity ^0.6.0;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; // todo: maybe remove?
|
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; // todo: maybe remove?
|
||||||
|
|
||||||
interface IVerifier {
|
interface IVerifier {
|
||||||
function verifyProof(bytes memory _proof, uint256[10] memory _input) external returns(bool);
|
function verifyProof(bytes memory _proof, uint256[9] memory _input) external returns(bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract TornadoPool is ReentrancyGuard {
|
contract TornadoPool is ReentrancyGuard {
|
||||||
@ -27,8 +28,15 @@ contract TornadoPool is ReentrancyGuard {
|
|||||||
uint public currentCommitmentIndex;
|
uint public currentCommitmentIndex;
|
||||||
IVerifier public verifier;
|
IVerifier public verifier;
|
||||||
|
|
||||||
|
struct ExtData {
|
||||||
|
address payable _recipient;
|
||||||
|
address payable _relayer;
|
||||||
|
bytes encryptedOutput1;
|
||||||
|
bytes encryptedOutput2;
|
||||||
|
}
|
||||||
|
|
||||||
// todo: event Transaction();
|
// todo: event Transaction();
|
||||||
event NewCommitment(bytes32 commitment, uint index);
|
event NewCommitment(bytes32 commitment, uint index, bytes encryptedOutput);
|
||||||
event NewNullifier(bytes32 nullifier);
|
event NewNullifier(bytes32 nullifier);
|
||||||
event Withdraw(bytes32 indexed nullifier); // todo emit it on withdraw so we can easily find the withdraw tx for user on UI
|
event Withdraw(bytes32 indexed nullifier); // todo emit it on withdraw so we can easily find the withdraw tx for user on UI
|
||||||
|
|
||||||
@ -49,14 +57,15 @@ contract TornadoPool is ReentrancyGuard {
|
|||||||
bytes32[2] calldata _outputCommitments,
|
bytes32[2] calldata _outputCommitments,
|
||||||
uint256 _extAmount,
|
uint256 _extAmount,
|
||||||
uint256 _fee,
|
uint256 _fee,
|
||||||
address payable _recipient,
|
ExtData calldata _extData,
|
||||||
address payable _relayer
|
bytes32 _extDataHash
|
||||||
)
|
)
|
||||||
external payable nonReentrant
|
external payable nonReentrant
|
||||||
{
|
{
|
||||||
require(currentRoot == _root, "Invalid merkle root");
|
require(currentRoot == _root, "Invalid merkle root");
|
||||||
require(!isSpent(_inputNullifiers[0]), "Input 0 is already spent");
|
require(!isSpent(_inputNullifiers[0]), "Input 0 is already spent");
|
||||||
require(!isSpent(_inputNullifiers[1]), "Input 1 is already spent");
|
require(!isSpent(_inputNullifiers[1]), "Input 1 is already spent");
|
||||||
|
require(uint256(_extDataHash) == uint256(keccak256(abi.encode(_extData))) % FIELD_SIZE, "Incorrect external data hash");
|
||||||
require(verifier.verifyProof(_proof, [
|
require(verifier.verifyProof(_proof, [
|
||||||
uint256(_root),
|
uint256(_root),
|
||||||
uint256(_newRoot),
|
uint256(_newRoot),
|
||||||
@ -66,8 +75,7 @@ contract TornadoPool is ReentrancyGuard {
|
|||||||
uint256(_outputCommitments[1]),
|
uint256(_outputCommitments[1]),
|
||||||
_extAmount,
|
_extAmount,
|
||||||
_fee,
|
_fee,
|
||||||
uint256(_recipient),
|
uint256(_extDataHash)
|
||||||
uint256(_relayer)
|
|
||||||
]), "Invalid transaction proof");
|
]), "Invalid transaction proof");
|
||||||
|
|
||||||
currentRoot = _newRoot;
|
currentRoot = _newRoot;
|
||||||
@ -79,16 +87,16 @@ contract TornadoPool is ReentrancyGuard {
|
|||||||
require(msg.value == uint256(extAmount), "Incorrect amount of ETH sent on deposit");
|
require(msg.value == uint256(extAmount), "Incorrect amount of ETH sent on deposit");
|
||||||
} else {
|
} else {
|
||||||
require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal");
|
require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal");
|
||||||
_recipient.transfer(uint256(-extAmount));
|
_extData._recipient.transfer(uint256(-extAmount));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_fee > 0) {
|
if (_fee > 0) {
|
||||||
_recipient.transfer(_fee);
|
_extData._relayer.transfer(_fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo enforce currentCommitmentIndex value in snark
|
// todo enforce currentCommitmentIndex value in snark
|
||||||
emit NewCommitment(_outputCommitments[0], currentCommitmentIndex++);
|
emit NewCommitment(_outputCommitments[0], currentCommitmentIndex++, _extData.encryptedOutput1);
|
||||||
emit NewCommitment(_outputCommitments[1], currentCommitmentIndex++);
|
emit NewCommitment(_outputCommitments[1], currentCommitmentIndex++, _extData.encryptedOutput2);
|
||||||
emit NewNullifier(_inputNullifiers[0]);
|
emit NewNullifier(_inputNullifiers[0]);
|
||||||
emit NewNullifier(_inputNullifiers[1]);
|
emit NewNullifier(_inputNullifiers[1]);
|
||||||
// emit Transaction();
|
// emit Transaction();
|
||||||
@ -110,14 +118,4 @@ contract TornadoPool is ReentrancyGuard {
|
|||||||
function isSpent(bytes32 _nullifierHash) public view returns(bool) {
|
function isSpent(bytes32 _nullifierHash) public view returns(bool) {
|
||||||
return nullifierHashes[_nullifierHash];
|
return nullifierHashes[_nullifierHash];
|
||||||
}
|
}
|
||||||
|
|
||||||
// /** @dev whether an array of notes is already spent */
|
|
||||||
// function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns(bool[] memory spent) {
|
|
||||||
// spent = new bool[](_nullifierHashes.length);
|
|
||||||
// for(uint i = 0; i < _nullifierHashes.length; i++) {
|
|
||||||
// if (isSpent(_nullifierHashes[i])) {
|
|
||||||
// spent[i] = true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
"test": "test"
|
"test": "test"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:circuit": "./scripts/buildCircuit.sh transaction",
|
"circuit": "./scripts/buildCircuit.sh transaction",
|
||||||
"build:contract": "npx hardhat compile",
|
"compile": "npx hardhat compile",
|
||||||
"build": "npm run build:circuit && npm run build:contract",
|
"build": "npm run circuit && npm run compile",
|
||||||
"migrate": "npx hardhat run scripts/deploy.js --network localhost",
|
"migrate": "npx hardhat run scripts/deploy.js --network localhost",
|
||||||
"start": "node ./src/index.js"
|
"start": "node ./src/index.js"
|
||||||
},
|
},
|
||||||
|
113
src/index.js
113
src/index.js
@ -1,8 +1,5 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
const MerkleTree = require('fixed-merkle-tree')
|
const MerkleTree = require('fixed-merkle-tree')
|
||||||
const fs = require('fs')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
const { poseidon } = require('circomlib')
|
|
||||||
const Web3 = require('web3')
|
const Web3 = require('web3')
|
||||||
const { ethers } = require('hardhat')
|
const { ethers } = require('hardhat')
|
||||||
const { BigNumber } = ethers
|
const { BigNumber } = ethers
|
||||||
@ -85,64 +82,86 @@ async function insertOutput(tree, output) {
|
|||||||
output.merklePathElements = pathElements
|
output.merklePathElements = pathElements
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deposit() {
|
async function getProof({ input1, input2, output1, output2, tree, extAmount, fee, recipient, relayer }) {
|
||||||
const amount = 1e6
|
const oldRoot = tree.root()
|
||||||
const tree = await buildMerkleTree()
|
|
||||||
const oldRoot = await tree.root()
|
|
||||||
const keypair = randomKeypair()
|
|
||||||
const tx = createDeposit(amount, keypair)
|
|
||||||
await insertOutput(tree, tx.outputs[0])
|
|
||||||
await insertOutput(tree, tx.outputs[1])
|
|
||||||
console.log('Note', tx.outputs[0])
|
|
||||||
|
|
||||||
let input = {
|
// if deposit require(extAmount > 0)
|
||||||
root: oldRoot,
|
|
||||||
newRoot: await tree.root(),
|
|
||||||
inputNullifier: [tx.inputs[0].nullifier, tx.inputs[1].nullifier],
|
|
||||||
outputCommitment: [tx.outputs[0].commitment, tx.outputs[1].commitment],
|
|
||||||
extAmount: amount,
|
|
||||||
fee: 0,
|
|
||||||
recipient: 0,
|
|
||||||
relayer: 0,
|
|
||||||
|
|
||||||
// private inputs
|
if (input1.amount !== 0) {
|
||||||
privateKey: tx.inputs[0].privkey,
|
// transact
|
||||||
|
const index1 = await tree.indexOf(toFixedHex(input1.commitment))
|
||||||
// data for 2 transaction inputs
|
const path1 = await tree.path(index1)
|
||||||
inAmount: [tx.inputs[0].amount, tx.inputs[1].amount],
|
|
||||||
inBlinding: [tx.inputs[0].blinding, tx.inputs[1].blinding],
|
|
||||||
inPathIndices: [
|
|
||||||
bitsToNumber(tx.inputs[0].merklePathIndices),
|
|
||||||
bitsToNumber(tx.inputs[1].merklePathIndices),
|
|
||||||
],
|
|
||||||
inPathElements: [tx.inputs[0].merklePathElements, tx.inputs[1].merklePathElements],
|
|
||||||
|
|
||||||
// data for 2 transaction outputs
|
|
||||||
outAmount: [tx.outputs[0].amount, tx.outputs[1].amount],
|
|
||||||
outBlinding: [tx.outputs[0].blinding, tx.outputs[1].blinding],
|
|
||||||
outPubkey: [tx.outputs[0].pubkey, tx.outputs[1].pubkey],
|
|
||||||
outPathIndices: bitsToNumber(tx.outputs[0].merklePathIndices.slice(1)),
|
|
||||||
outPathElements: tx.outputs[0].merklePathElements.slice(1),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log('input', JSON.stringify(stringifyBigInts(input)))
|
await insertOutput(tree, output1)
|
||||||
console.log('DEPOSIT input', input)
|
await insertOutput(tree, output2)
|
||||||
|
|
||||||
|
extData = recipient + relayer // TODO
|
||||||
|
let input = {
|
||||||
|
root: oldRoot,
|
||||||
|
newRoot: tree.root(),
|
||||||
|
inputNullifier: [input1.nullifier, input2.nullifier],
|
||||||
|
outputCommitment: [outputs1.commitment, outputs2.commitment],
|
||||||
|
extAmount,
|
||||||
|
fee,
|
||||||
|
extData,
|
||||||
|
|
||||||
|
// private inputs
|
||||||
|
privateKey: inputs1.privkey, // TODO make sure you use the right one when you shuffle inputs
|
||||||
|
|
||||||
|
// data for 2 transaction inputs
|
||||||
|
inAmount: [inputs1.amount, inputs2.amount],
|
||||||
|
inBlinding: [inputs1.blinding, inputs2.blinding],
|
||||||
|
inPathIndices: [bitsToNumber(inputs1.merklePathIndices), bitsToNumber(inputs2.merklePathIndices)],
|
||||||
|
inPathElements: [inputs1.merklePathElements, inputs2.merklePathElements],
|
||||||
|
|
||||||
|
// data for 2 transaction outputs
|
||||||
|
outAmount: [outputs1.amount, outputs2.amount],
|
||||||
|
outBlinding: [outputs1.blinding, outputs2.blinding],
|
||||||
|
outPubkey: [outputs1.pubkey, outputs2.pubkey],
|
||||||
|
outPathIndices: bitsToNumber[outputs1.merklePathIndices.slice(1)],
|
||||||
|
outPathElements: [outputs1.merklePathElements.slice(1)],
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('SNARK input', input)
|
||||||
|
|
||||||
console.log('Generating SNARK proof...')
|
console.log('Generating SNARK proof...')
|
||||||
|
|
||||||
const proof = await prove(input, './artifacts/circuits/transaction')
|
const proof = await prove(input, './artifacts/circuits/transaction')
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
toFixedHex(input.root),
|
toFixedHex(input.root),
|
||||||
toFixedHex(input.newRoot),
|
toFixedHex(input.newRoot),
|
||||||
[toFixedHex(tx.inputs[0].nullifier), toFixedHex(tx.inputs[1].nullifier)],
|
[toFixedHex(input1.nullifier), toFixedHex(input2.nullifier)],
|
||||||
[toFixedHex(tx.outputs[0].commitment), toFixedHex(tx.outputs[1].commitment)],
|
[toFixedHex(output1.commitment), toFixedHex(output2.commitment)],
|
||||||
toFixedHex(amount),
|
toFixedHex(0),
|
||||||
toFixedHex(input.fee),
|
toFixedHex(input.fee),
|
||||||
toFixedHex(input.recipient, 20),
|
toFixedHex(extData), // extData hash actually
|
||||||
toFixedHex(input.relayer, 20),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
proof,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deposit() {
|
||||||
|
const amount = 1e6
|
||||||
|
const tree = await buildMerkleTree()
|
||||||
|
const keypair = randomKeypair()
|
||||||
|
const tx = createDeposit(amount, keypair)
|
||||||
|
|
||||||
|
const { proof, args } = await getProof({
|
||||||
|
input1: tx.inputs[0],
|
||||||
|
input2: tx.inputs[1],
|
||||||
|
output1: tx.outputs[1],
|
||||||
|
output2: tx.outputs[1],
|
||||||
|
tree,
|
||||||
|
extAmount: amount,
|
||||||
|
fee: 0,
|
||||||
|
recipient: 0,
|
||||||
|
relayer: 0,
|
||||||
|
})
|
||||||
|
|
||||||
console.log('Sending deposit transaction...')
|
console.log('Sending deposit transaction...')
|
||||||
const receipt = await contract.methods
|
const receipt = await contract.methods
|
||||||
.transaction(proof, ...args)
|
.transaction(proof, ...args)
|
||||||
|
@ -6,8 +6,6 @@ const tmp = require('tmp-promise')
|
|||||||
const util = require('util')
|
const util = require('util')
|
||||||
const exec = util.promisify(require('child_process').exec)
|
const exec = util.promisify(require('child_process').exec)
|
||||||
|
|
||||||
function stringify() {}
|
|
||||||
|
|
||||||
function prove(input, keyBasePath) {
|
function prove(input, keyBasePath) {
|
||||||
input = utils.stringifyBigInts(input)
|
input = utils.stringifyBigInts(input)
|
||||||
console.log('input', input)
|
console.log('input', input)
|
||||||
|
15
src/utils.js
15
src/utils.js
@ -6,9 +6,22 @@ const { poseidon } = require('circomlib')
|
|||||||
const poseidonHash = (items) => BigNumber.from(poseidon(items).toString())
|
const poseidonHash = (items) => BigNumber.from(poseidon(items).toString())
|
||||||
const poseidonHash2 = (a, b) => poseidonHash([a, b])
|
const poseidonHash2 = (a, b) => poseidonHash([a, b])
|
||||||
|
|
||||||
|
const FIELD_SIZE = BigNumber.from(
|
||||||
|
'21888242871839275222246405745257275088548364400416034343698204186575808495617',
|
||||||
|
)
|
||||||
/** Generate random number of specified byte length */
|
/** Generate random number of specified byte length */
|
||||||
const randomBN = (nbytes = 31) => BigNumber.from(crypto.randomBytes(nbytes))
|
const randomBN = (nbytes = 31) => BigNumber.from(crypto.randomBytes(nbytes))
|
||||||
|
|
||||||
|
function getExtDataHash({ recipient, relayer, encryptedOutput1, encryptedOutput2 }) {
|
||||||
|
const encodedData = abi.encode(
|
||||||
|
['address', 'address', 'bytes', 'bytes'],
|
||||||
|
[toFixedHex(recipient, 20), toFixedHex(relayer, 20), encryptedOutput1, encryptedOutput2],
|
||||||
|
)
|
||||||
|
const hash = ethers.utils.keccak256(encodedData)
|
||||||
|
|
||||||
|
return BigNumber.from(hash).mod(FIELD_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
/** BigNumber to hex string of specified length */
|
/** BigNumber to hex string of specified length */
|
||||||
const toFixedHex = (number, length = 32) =>
|
const toFixedHex = (number, length = 32) =>
|
||||||
'0x' +
|
'0x' +
|
||||||
@ -35,10 +48,12 @@ function bitsToNumber(bits) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
FIELD_SIZE,
|
||||||
randomBN,
|
randomBN,
|
||||||
bitsToNumber,
|
bitsToNumber,
|
||||||
toFixedHex,
|
toFixedHex,
|
||||||
toBuffer,
|
toBuffer,
|
||||||
poseidonHash,
|
poseidonHash,
|
||||||
poseidonHash2,
|
poseidonHash2,
|
||||||
|
getExtDataHash,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user