tornado-nova/contracts/TornadoPool.sol
2021-06-16 14:01:29 +03:00

190 lines
6.6 KiB
Solidity

// SPDX-License-Identifier: MIT
// https://tornado.cash
/*
* d888888P dP a88888b. dP
* 88 88 d8' `88 88
* 88 .d8888b. 88d888b. 88d888b. .d8888b. .d888b88 .d8888b. 88 .d8888b. .d8888b. 88d888b.
* 88 88' `88 88' `88 88' `88 88' `88 88' `88 88' `88 88 88' `88 Y8ooooo. 88' `88
* 88 88. .88 88 88 88 88. .88 88. .88 88. .88 dP Y8. .88 88. .88 88 88 88
* dP `88888P' dP dP dP `88888P8 `88888P8 `88888P' 88 Y88888P' `88888P8 `88888P' dP dP
* ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
*/
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 view returns (bool);
function verifyProof(bytes memory _proof, uint256[24] memory _input) external view returns (bool);
}
contract TornadoPool is ReentrancyGuard {
uint256 public constant FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
uint256 public constant MAX_EXT_AMOUNT = 2**248 - 1;
mapping(bytes32 => bool) public nullifierHashes;
bytes32 public currentRoot;
uint256 public currentCommitmentIndex;
IVerifier public immutable verifier2;
IVerifier public immutable verifier16;
struct ExtData {
address payable recipient;
address payable relayer;
bytes encryptedOutput1;
bytes encryptedOutput2;
}
event NewCommitment(bytes32 commitment, uint256 index, bytes encryptedOutput);
event NewNullifier(bytes32 nullifier);
/**
@dev The constructor
@param _verifier2 the address of SNARK verifier for 2 inputs
@param _verifier16 the address of SNARK verifier for 16 inputs
*/
constructor(
IVerifier _verifier2,
IVerifier _verifier16,
bytes32 _currentRoot
) public {
verifier2 = _verifier2;
verifier16 = _verifier16;
currentRoot = _currentRoot;
}
function transaction(
bytes calldata _proof,
bytes32 _root,
bytes32 _newRoot,
bytes32[] calldata _inputNullifiers,
bytes32[2] calldata _outputCommitments,
uint256 _outPathIndices,
uint256 _extAmount,
uint256 _fee,
ExtData calldata _extData,
bytes32 _extDataHash
) external payable nonReentrant {
require(currentRoot == _root, "Invalid merkle root");
for (uint256 i = 0; i < _inputNullifiers.length; i++) {
require(!isSpent(_inputNullifiers[i]), "Input is already spent");
}
require(uint256(_extDataHash) == uint256(keccak256(abi.encode(_extData))) % FIELD_SIZE, "Incorrect external data hash");
require(_outPathIndices == currentCommitmentIndex >> 1, "Invalid merkle tree insert position");
require(
verifyProof(_proof, _root, _newRoot, _inputNullifiers, _outputCommitments, _outPathIndices, _extAmount, _fee, _extDataHash),
"Invalid transaction proof"
);
currentRoot = _newRoot;
for (uint256 i = 0; i < _inputNullifiers.length; i++) {
nullifierHashes[_inputNullifiers[i]] = true;
}
int256 extAmount = calculateExternalAmount(_extAmount);
if (extAmount > 0) {
require(msg.value == uint256(extAmount), "Incorrect amount of ETH sent on deposit");
} else if (extAmount < 0) {
require(msg.value == 0, "Sent ETH amount should be 0 for withdrawal");
require(_extData.recipient != address(0), "Can't withdraw to zero address");
_extData.recipient.transfer(uint256(-extAmount));
} else {
require(msg.value == 0, "Sent ETH amount should be 0 for transaction");
}
if (_fee > 0) {
_extData.relayer.transfer(_fee);
}
emit NewCommitment(_outputCommitments[0], currentCommitmentIndex++, _extData.encryptedOutput1);
emit NewCommitment(_outputCommitments[1], currentCommitmentIndex++, _extData.encryptedOutput2);
for (uint256 i = 0; i < _inputNullifiers.length; i++) {
emit NewNullifier(_inputNullifiers[i]);
}
}
function calculateExternalAmount(uint256 _extAmount) public pure returns (int256) {
// -MAX_EXT_AMOUNT < extAmount < MAX_EXT_AMOUNT
if (_extAmount < MAX_EXT_AMOUNT) {
return int256(_extAmount);
} else if (_extAmount > FIELD_SIZE - MAX_EXT_AMOUNT) {
// FIELD_SIZE - MAX_EXT_AMOUNT < _extAmount < FIELD_SIZE
return -(int256(FIELD_SIZE) - int256(_extAmount));
} else {
revert("Invalid extAmount value");
}
}
/** @dev whether a note is already spent */
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 _outPathIndices,
uint256 _extAmount,
uint256 _fee,
bytes32 _extDataHash
) public view returns (bool) {
if (_inputNullifiers.length == 2) {
return
verifier2.verifyProof(
_proof,
[
uint256(_root),
uint256(_newRoot),
_extAmount,
_fee,
uint256(_extDataHash),
uint256(_inputNullifiers[0]),
uint256(_inputNullifiers[1]),
uint256(_outputCommitments[0]),
uint256(_outputCommitments[1]),
_outPathIndices
]
);
} else if (_inputNullifiers.length == 16) {
return
verifier16.verifyProof(
_proof,
[
uint256(_root),
uint256(_newRoot),
_extAmount,
_fee,
uint256(_extDataHash),
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]),
_outPathIndices
]
);
} else {
revert("unsupported input count");
}
}
}