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? import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; // todo: maybe remove?
interface IVerifier { 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 { contract TornadoPool is ReentrancyGuard {
@ -38,15 +38,13 @@ contract TornadoPool is ReentrancyGuard {
bytes encryptedOutput2; bytes encryptedOutput2;
} }
// todo: event Transaction();
event NewCommitment(bytes32 commitment, uint256 index, bytes encryptedOutput); event NewCommitment(bytes32 commitment, uint256 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
/** /**
@dev The constructor @dev The constructor
@param _verifier2 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 this contract @param _verifier16 the address of SNARK verifier for 16 inputs
*/ */
constructor( constructor(
IVerifier _verifier2, IVerifier _verifier2,
@ -74,59 +72,10 @@ contract TornadoPool is ReentrancyGuard {
require(!isSpent(_inputNullifiers[i]), "Input is already spent"); require(!isSpent(_inputNullifiers[i]), "Input is already spent");
} }
require(uint256(_extDataHash) == uint256(keccak256(abi.encode(_extData))) % FIELD_SIZE, "Incorrect external data hash"); require(uint256(_extDataHash) == uint256(keccak256(abi.encode(_extData))) % FIELD_SIZE, "Incorrect external data hash");
if (_inputNullifiers.length == 2) { require(
require( verifyProof(_proof, _root, _newRoot, _inputNullifiers, _outputCommitments, _extAmount, _fee, _extDataHash),
verifier2.verifyProof( "Invalid transaction proof"
_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");
}
currentRoot = _newRoot; currentRoot = _newRoot;
for (uint256 i = 0; i < _inputNullifiers.length; i++) { for (uint256 i = 0; i < _inputNullifiers.length; i++) {
@ -172,4 +121,65 @@ 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];
} }
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 const MERKLE_TREE_HEIGHT = 5
async function buildMerkleTree({ tornadoPool }) { async function buildMerkleTree({ tornadoPool }) {
console.log('Getting contract state...')
const filter = tornadoPool.filters.NewCommitment() const filter = tornadoPool.filters.NewCommitment()
const events = await tornadoPool.queryFilter(filter, 0) 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 outputIndex = tree.elements().length - 1
const outputPath = tree.path(outputIndex).pathElements const outputPath = tree.path(outputIndex).pathElements
//encrypt(encryptedPublicKey, { data }, 'x25519-xsalsa20-poly1305')
const extData = { const extData = {
recipient: toFixedHex(recipient, 20), recipient: toFixedHex(recipient, 20),
relayer: toFixedHex(relayer, 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)), 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 proof = await prove(input, `./artifacts/circuits/transaction${inputs.length}`)
const args = [ const args = [
@ -119,7 +113,8 @@ async function transaction({ tornadoPool, inputs = [], outputs = [], fee = 0, re
let extAmount = BigNumber.from(fee) let extAmount = BigNumber.from(fee)
.add(outputs.reduce((sum, x) => sum.add(x.amount), BigNumber.from(0))) .add(outputs.reduce((sum, x) => sum.add(x.amount), BigNumber.from(0)))
.sub(inputs.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) { if (extAmount < 0) {
extAmount = FIELD_SIZE.add(extAmount) extAmount = FIELD_SIZE.add(extAmount)
} }
@ -134,12 +129,12 @@ async function transaction({ tornadoPool, inputs = [], outputs = [], fee = 0, re
relayer, relayer,
}) })
console.log('Sending transaction...')
const receipt = await tornadoPool.transaction(proof, ...args, { const receipt = await tornadoPool.transaction(proof, ...args, {
value: amount, value: amount,
gasLimit: 1e6, gasLimit: 1e6,
}) })
console.log(`Receipt ${receipt.hash}`) const { gasUsed } = await receipt.wait()
// console.log(`Gas Used ${gasUsed}`)
} }
module.exports = { transaction } module.exports = { transaction }

View File

@ -4,7 +4,7 @@ const { randomBN, poseidonHash, toBuffer } = require('./utils')
const Keypair = require('./keypair') const Keypair = require('./keypair')
class Utxo { 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} amount UTXO amount
* @param {BigNumber | BigInt | number | string} blinding Blinding factor * @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) const bobReceiveUtxo = Utxo.decrypt(bobKeypair, events[0].args.encryptedOutput, events[0].args.index)
expect(bobReceiveUtxo.amount).to.be.equal(bobSendAmount) 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 bobWithdrawAmount = 2e6
const bobEthAddress = '0xDeaD00000000000000000000000000000000BEEf' const bobEthAddress = '0xDeaD00000000000000000000000000000000BEEf'
const bobChangeUtxo = new Utxo({ amount: bobSendAmount - bobWithdrawAmount, keypair: bobKeypair }) const bobChangeUtxo = new Utxo({ amount: bobSendAmount - bobWithdrawAmount, keypair: bobKeypair })