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
|
||||
* utxo data encryption for recipient
|
||||
* poseidon
|
||||
* 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
|
||||
* relayer
|
||||
* race condition ?
|
||||
|
@ -24,8 +24,7 @@ template Transaction(levels, zeroLeaf) {
|
||||
// correct extAmount range is enforced on the smart contract
|
||||
signal input extAmount;
|
||||
signal input fee;
|
||||
signal input recipient;
|
||||
signal input relayer;
|
||||
signal input extDataHash;
|
||||
|
||||
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);
|
||||
|
@ -11,11 +11,12 @@
|
||||
*/
|
||||
|
||||
pragma solidity ^0.6.0;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; // todo: maybe remove?
|
||||
|
||||
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 {
|
||||
@ -27,8 +28,15 @@ contract TornadoPool is ReentrancyGuard {
|
||||
uint public currentCommitmentIndex;
|
||||
IVerifier public verifier;
|
||||
|
||||
struct ExtData {
|
||||
address payable _recipient;
|
||||
address payable _relayer;
|
||||
bytes encryptedOutput1;
|
||||
bytes encryptedOutput2;
|
||||
}
|
||||
|
||||
// todo: event Transaction();
|
||||
event NewCommitment(bytes32 commitment, uint index);
|
||||
event NewCommitment(bytes32 commitment, uint index, bytes encryptedOutput);
|
||||
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
|
||||
|
||||
@ -49,14 +57,15 @@ contract TornadoPool is ReentrancyGuard {
|
||||
bytes32[2] calldata _outputCommitments,
|
||||
uint256 _extAmount,
|
||||
uint256 _fee,
|
||||
address payable _recipient,
|
||||
address payable _relayer
|
||||
ExtData calldata _extData,
|
||||
bytes32 _extDataHash
|
||||
)
|
||||
external payable nonReentrant
|
||||
{
|
||||
require(currentRoot == _root, "Invalid merkle root");
|
||||
require(!isSpent(_inputNullifiers[0]), "Input 0 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, [
|
||||
uint256(_root),
|
||||
uint256(_newRoot),
|
||||
@ -66,8 +75,7 @@ contract TornadoPool is ReentrancyGuard {
|
||||
uint256(_outputCommitments[1]),
|
||||
_extAmount,
|
||||
_fee,
|
||||
uint256(_recipient),
|
||||
uint256(_relayer)
|
||||
uint256(_extDataHash)
|
||||
]), "Invalid transaction proof");
|
||||
|
||||
currentRoot = _newRoot;
|
||||
@ -79,16 +87,16 @@ contract TornadoPool is ReentrancyGuard {
|
||||
require(msg.value == uint256(extAmount), "Incorrect amount of ETH sent on deposit");
|
||||
} else {
|
||||
require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal");
|
||||
_recipient.transfer(uint256(-extAmount));
|
||||
_extData._recipient.transfer(uint256(-extAmount));
|
||||
}
|
||||
|
||||
if (_fee > 0) {
|
||||
_recipient.transfer(_fee);
|
||||
_extData._relayer.transfer(_fee);
|
||||
}
|
||||
|
||||
// todo enforce currentCommitmentIndex value in snark
|
||||
emit NewCommitment(_outputCommitments[0], currentCommitmentIndex++);
|
||||
emit NewCommitment(_outputCommitments[1], currentCommitmentIndex++);
|
||||
emit NewCommitment(_outputCommitments[0], currentCommitmentIndex++, _extData.encryptedOutput1);
|
||||
emit NewCommitment(_outputCommitments[1], currentCommitmentIndex++, _extData.encryptedOutput2);
|
||||
emit NewNullifier(_inputNullifiers[0]);
|
||||
emit NewNullifier(_inputNullifiers[1]);
|
||||
// emit Transaction();
|
||||
@ -110,14 +118,4 @@ contract TornadoPool is ReentrancyGuard {
|
||||
function isSpent(bytes32 _nullifierHash) public view returns(bool) {
|
||||
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"
|
||||
},
|
||||
"scripts": {
|
||||
"build:circuit": "./scripts/buildCircuit.sh transaction",
|
||||
"build:contract": "npx hardhat compile",
|
||||
"build": "npm run build:circuit && npm run build:contract",
|
||||
"circuit": "./scripts/buildCircuit.sh transaction",
|
||||
"compile": "npx hardhat compile",
|
||||
"build": "npm run circuit && npm run compile",
|
||||
"migrate": "npx hardhat run scripts/deploy.js --network localhost",
|
||||
"start": "node ./src/index.js"
|
||||
},
|
||||
|
113
src/index.js
113
src/index.js
@ -1,8 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
const MerkleTree = require('fixed-merkle-tree')
|
||||
const fs = require('fs')
|
||||
const crypto = require('crypto')
|
||||
const { poseidon } = require('circomlib')
|
||||
const Web3 = require('web3')
|
||||
const { ethers } = require('hardhat')
|
||||
const { BigNumber } = ethers
|
||||
@ -85,64 +82,86 @@ async function insertOutput(tree, output) {
|
||||
output.merklePathElements = pathElements
|
||||
}
|
||||
|
||||
async function deposit() {
|
||||
const amount = 1e6
|
||||
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])
|
||||
async function getProof({ input1, input2, output1, output2, tree, extAmount, fee, recipient, relayer }) {
|
||||
const oldRoot = tree.root()
|
||||
|
||||
let input = {
|
||||
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,
|
||||
// if deposit require(extAmount > 0)
|
||||
|
||||
// private inputs
|
||||
privateKey: tx.inputs[0].privkey,
|
||||
|
||||
// data for 2 transaction inputs
|
||||
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),
|
||||
if (input1.amount !== 0) {
|
||||
// transact
|
||||
const index1 = await tree.indexOf(toFixedHex(input1.commitment))
|
||||
const path1 = await tree.path(index1)
|
||||
}
|
||||
|
||||
// console.log('input', JSON.stringify(stringifyBigInts(input)))
|
||||
console.log('DEPOSIT input', input)
|
||||
await insertOutput(tree, output1)
|
||||
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...')
|
||||
|
||||
const proof = await prove(input, './artifacts/circuits/transaction')
|
||||
|
||||
const args = [
|
||||
toFixedHex(input.root),
|
||||
toFixedHex(input.newRoot),
|
||||
[toFixedHex(tx.inputs[0].nullifier), toFixedHex(tx.inputs[1].nullifier)],
|
||||
[toFixedHex(tx.outputs[0].commitment), toFixedHex(tx.outputs[1].commitment)],
|
||||
toFixedHex(amount),
|
||||
[toFixedHex(input1.nullifier), toFixedHex(input2.nullifier)],
|
||||
[toFixedHex(output1.commitment), toFixedHex(output2.commitment)],
|
||||
toFixedHex(0),
|
||||
toFixedHex(input.fee),
|
||||
toFixedHex(input.recipient, 20),
|
||||
toFixedHex(input.relayer, 20),
|
||||
toFixedHex(extData), // extData hash actually
|
||||
]
|
||||
|
||||
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...')
|
||||
const receipt = await contract.methods
|
||||
.transaction(proof, ...args)
|
||||
|
@ -6,8 +6,6 @@ const tmp = require('tmp-promise')
|
||||
const util = require('util')
|
||||
const exec = util.promisify(require('child_process').exec)
|
||||
|
||||
function stringify() {}
|
||||
|
||||
function prove(input, keyBasePath) {
|
||||
input = utils.stringifyBigInts(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 poseidonHash2 = (a, b) => poseidonHash([a, b])
|
||||
|
||||
const FIELD_SIZE = BigNumber.from(
|
||||
'21888242871839275222246405745257275088548364400416034343698204186575808495617',
|
||||
)
|
||||
/** Generate random number of specified byte length */
|
||||
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 */
|
||||
const toFixedHex = (number, length = 32) =>
|
||||
'0x' +
|
||||
@ -35,10 +48,12 @@ function bitsToNumber(bits) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FIELD_SIZE,
|
||||
randomBN,
|
||||
bitsToNumber,
|
||||
toFixedHex,
|
||||
toBuffer,
|
||||
poseidonHash,
|
||||
poseidonHash2,
|
||||
getExtDataHash,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user