This commit is contained in:
Alexey 2021-06-16 11:28:39 +03:00
parent 4fded51106
commit 1dd9cf70d9
4 changed files with 75 additions and 70 deletions

View File

@ -16,9 +16,9 @@ pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; // todo: maybe remove?
interface IVerifier {
function verifyProof(bytes memory _proof, uint256[9] memory _input) external returns (bool);
function verifyProof(bytes memory _proof, uint256[9] memory _input) external view returns (bool);
function verifyProof(bytes memory _proof, uint256[23] memory _input) external returns (bool);
function verifyProof(bytes memory _proof, uint256[23] memory _input) external view returns (bool);
}
contract TornadoPool is ReentrancyGuard {
@ -38,15 +38,13 @@ contract TornadoPool is ReentrancyGuard {
bytes encryptedOutput2;
}
// todo: event Transaction();
event NewCommitment(bytes32 commitment, uint256 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
/**
@dev The constructor
@param _verifier2 the address of SNARK verifier for this contract
@param _verifier16 the address of SNARK verifier for this contract
@param _verifier2 the address of SNARK verifier for 2 inputs
@param _verifier16 the address of SNARK verifier for 16 inputs
*/
constructor(
IVerifier _verifier2,
@ -74,59 +72,10 @@ contract TornadoPool is ReentrancyGuard {
require(!isSpent(_inputNullifiers[i]), "Input is already spent");
}
require(uint256(_extDataHash) == uint256(keccak256(abi.encode(_extData))) % FIELD_SIZE, "Incorrect external data hash");
if (_inputNullifiers.length == 2) {
require(
verifier2.verifyProof(
_proof,
[
uint256(_root),
uint256(_newRoot),
uint256(_inputNullifiers[0]),
uint256(_inputNullifiers[1]),
uint256(_outputCommitments[0]),
uint256(_outputCommitments[1]),
_extAmount,
_fee,
uint256(_extDataHash)
]
),
"Invalid transaction proof"
);
} else if (_inputNullifiers.length == 16) {
require(
verifier16.verifyProof(
_proof,
[
uint256(_root),
uint256(_newRoot),
uint256(_inputNullifiers[0]),
uint256(_inputNullifiers[1]),
uint256(_inputNullifiers[2]),
uint256(_inputNullifiers[3]),
uint256(_inputNullifiers[4]),
uint256(_inputNullifiers[5]),
uint256(_inputNullifiers[6]),
uint256(_inputNullifiers[7]),
uint256(_inputNullifiers[8]),
uint256(_inputNullifiers[9]),
uint256(_inputNullifiers[10]),
uint256(_inputNullifiers[11]),
uint256(_inputNullifiers[12]),
uint256(_inputNullifiers[13]),
uint256(_inputNullifiers[14]),
uint256(_inputNullifiers[15]),
uint256(_outputCommitments[0]),
uint256(_outputCommitments[1]),
_extAmount,
_fee,
uint256(_extDataHash)
]
),
"Invalid transaction proof"
);
} else {
revert("unsupported input count");
}
require(
verifyProof(_proof, _root, _newRoot, _inputNullifiers, _outputCommitments, _extAmount, _fee, _extDataHash),
"Invalid transaction proof"
);
currentRoot = _newRoot;
for (uint256 i = 0; i < _inputNullifiers.length; i++) {
@ -172,4 +121,65 @@ contract TornadoPool is ReentrancyGuard {
function isSpent(bytes32 _nullifierHash) public view returns (bool) {
return nullifierHashes[_nullifierHash];
}
function verifyProof(
bytes memory _proof,
bytes32 _root,
bytes32 _newRoot,
bytes32[] memory _inputNullifiers,
bytes32[2] memory _outputCommitments,
uint256 _extAmount,
uint256 _fee,
bytes32 _extDataHash
) public view returns (bool) {
if (_inputNullifiers.length == 2) {
return
verifier2.verifyProof(
_proof,
[
uint256(_root),
uint256(_newRoot),
uint256(_inputNullifiers[0]),
uint256(_inputNullifiers[1]),
uint256(_outputCommitments[0]),
uint256(_outputCommitments[1]),
_extAmount,
_fee,
uint256(_extDataHash)
]
);
} else if (_inputNullifiers.length == 16) {
return
verifier16.verifyProof(
_proof,
[
uint256(_root),
uint256(_newRoot),
uint256(_inputNullifiers[0]),
uint256(_inputNullifiers[1]),
uint256(_inputNullifiers[2]),
uint256(_inputNullifiers[3]),
uint256(_inputNullifiers[4]),
uint256(_inputNullifiers[5]),
uint256(_inputNullifiers[6]),
uint256(_inputNullifiers[7]),
uint256(_inputNullifiers[8]),
uint256(_inputNullifiers[9]),
uint256(_inputNullifiers[10]),
uint256(_inputNullifiers[11]),
uint256(_inputNullifiers[12]),
uint256(_inputNullifiers[13]),
uint256(_inputNullifiers[14]),
uint256(_inputNullifiers[15]),
uint256(_outputCommitments[0]),
uint256(_outputCommitments[1]),
_extAmount,
_fee,
uint256(_extDataHash)
]
);
} else {
revert("unsupported input count");
}
}
}

View File

@ -9,7 +9,6 @@ const { prove } = require('./prover')
const MERKLE_TREE_HEIGHT = 5
async function buildMerkleTree({ tornadoPool }) {
console.log('Getting contract state...')
const filter = tornadoPool.filters.NewCommitment()
const events = await tornadoPool.queryFilter(filter, 0)
@ -48,8 +47,6 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
const outputIndex = tree.elements().length - 1
const outputPath = tree.path(outputIndex).pathElements
//encrypt(encryptedPublicKey, { data }, 'x25519-xsalsa20-poly1305')
const extData = {
recipient: toFixedHex(recipient, 20),
relayer: toFixedHex(relayer, 20),
@ -82,9 +79,6 @@ async function getProof({ inputs, outputs, tree, extAmount, fee, recipient, rela
outPathElements: outputPath.slice(Math.log2(outputs.length)),
}
//console.log('SNARK input', input)
console.log('Generating SNARK proof...')
const proof = await prove(input, `./artifacts/circuits/transaction${inputs.length}`)
const args = [
@ -119,7 +113,8 @@ async function transaction({ tornadoPool, inputs = [], outputs = [], fee = 0, re
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)))
const amount = extAmount > 0 ? extAmount : 0
const amount = extAmount > 0 ? extAmount : 0 // extAmount will be positive for a deposit, zero for a transact and negative for withdraw
if (extAmount < 0) {
extAmount = FIELD_SIZE.add(extAmount)
}
@ -134,12 +129,12 @@ async function transaction({ tornadoPool, inputs = [], outputs = [], fee = 0, re
relayer,
})
console.log('Sending transaction...')
const receipt = await tornadoPool.transaction(proof, ...args, {
value: amount,
gasLimit: 1e6,
})
console.log(`Receipt ${receipt.hash}`)
const { gasUsed } = await receipt.wait()
// console.log(`Gas Used ${gasUsed}`)
}
module.exports = { transaction }

View File

@ -4,7 +4,7 @@ const { randomBN, poseidonHash, toBuffer } = require('./utils')
const Keypair = require('./keypair')
class Utxo {
/**
/** Initialize a new UTXO - unspent transaction output or input. Note, a full TX consists of 2/16 inputs and 2 outputs
*
* @param {BigNumber | BigInt | number | string} amount UTXO amount
* @param {BigNumber | BigInt | number | string} blinding Blinding factor

View File

@ -68,7 +68,7 @@ describe('TornadoPool', () => {
const bobReceiveUtxo = Utxo.decrypt(bobKeypair, events[0].args.encryptedOutput, events[0].args.index)
expect(bobReceiveUtxo.amount).to.be.equal(bobSendAmount)
// Bob withdraws part of his funds from the shielded pool
// Bob withdraws a part of his funds from the shielded pool
const bobWithdrawAmount = 2e6
const bobEthAddress = '0xDeaD00000000000000000000000000000000BEEf'
const bobChangeUtxo = new Utxo({ amount: bobSendAmount - bobWithdrawAmount, keypair: bobKeypair })