mirror of
https://github.com/tornadocash/tornado-core.git
synced 2025-01-05 10:50:46 -05:00
init
This commit is contained in:
parent
5ef6e33c78
commit
11f7408def
3
.gitignore
vendored
3
.gitignore
vendored
@ -96,3 +96,6 @@ typings/
|
||||
|
||||
ERC20Mixer_flat.sol
|
||||
ETHMixer_flat.sol
|
||||
|
||||
.openzeppelin/.session
|
||||
.openzeppelin/dev-*.json
|
||||
|
15
.openzeppelin/project.json
Normal file
15
.openzeppelin/project.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"manifestVersion": "2.2",
|
||||
"contracts": {
|
||||
"ETHMixer": "ETHMixer"
|
||||
},
|
||||
"dependencies": {},
|
||||
"name": "tornado",
|
||||
"version": "1.0.0",
|
||||
"compiler": {
|
||||
"manager": "truffle",
|
||||
"compilerSettings": {
|
||||
"optimizer": {}
|
||||
}
|
||||
}
|
||||
}
|
@ -31,8 +31,7 @@ template Withdraw(levels, rounds) {
|
||||
signal input root;
|
||||
signal input nullifierHash;
|
||||
signal input receiver; // not taking part in any computations
|
||||
signal input relayer; // not taking part in any computations
|
||||
signal input fee; // not taking part in any computations
|
||||
|
||||
signal private input nullifier;
|
||||
signal private input secret;
|
||||
signal private input pathElements[levels];
|
||||
@ -56,11 +55,7 @@ template Withdraw(levels, rounds) {
|
||||
// Most likely it is not required, but it's better to stay on the safe side and it only takes 2 constraints
|
||||
// Squares are used to prevent optimizer from removing those constraints
|
||||
signal receiverSquare;
|
||||
signal feeSquare;
|
||||
signal relayerSquare;
|
||||
receiverSquare <== receiver * receiver;
|
||||
feeSquare <== fee * fee;
|
||||
relayerSquare <== relayer * relayer;
|
||||
}
|
||||
|
||||
component main = Withdraw(16, 220);
|
||||
|
98
cli.js
98
cli.js
@ -11,6 +11,8 @@ const merkleTree = require('./lib/MerkleTree')
|
||||
const Web3 = require('web3')
|
||||
const buildGroth16 = require('websnark/src/groth16')
|
||||
const websnarkUtils = require('websnark/src/utils')
|
||||
const { GSNProvider, GSNDevProvider } = require('@openzeppelin/gsn-provider')
|
||||
const { ephemeral } = require('@openzeppelin/network')
|
||||
|
||||
let web3, mixer, erc20mixer, circuit, proving_key, groth16, erc20
|
||||
let MERKLE_TREE_HEIGHT, ETH_AMOUNT, EMPTY_ELEMENT, ERC20_TOKEN
|
||||
@ -194,11 +196,83 @@ async function withdraw(note, receiver) {
|
||||
console.log('Done')
|
||||
}
|
||||
|
||||
async function withdrawViaRelayer(note, receiver) {
|
||||
// Decode hex string and restore the deposit object
|
||||
let buf = Buffer.from(note.slice(2), 'hex')
|
||||
let deposit = createDeposit(bigInt.leBuff2int(buf.slice(0, 31)), bigInt.leBuff2int(buf.slice(31, 62)))
|
||||
const nullifierHash = pedersenHash(deposit.nullifier.leInt2Buff(31))
|
||||
const paddedNullifierHash = nullifierHash.toString(16).padStart('66', '0x000000')
|
||||
const paddedCommitment = deposit.commitment.toString(16).padStart('66', '0x000000')
|
||||
|
||||
// Get all deposit events from smart contract and assemble merkle tree from them
|
||||
console.log('Getting current state from mixer contract')
|
||||
const events = await mixer.getPastEvents('Deposit', { fromBlock: mixer.deployedBlock, toBlock: 'latest' })
|
||||
const leaves = events
|
||||
.sort((a, b) => a.returnValues.leafIndex.sub(b.returnValues.leafIndex)) // Sort events in chronological order
|
||||
.map(e => e.returnValues.commitment)
|
||||
const tree = new merkleTree(MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, leaves)
|
||||
|
||||
// Find current commitment in the tree
|
||||
let depositEvent = events.find(e => e.returnValues.commitment.eq(paddedCommitment))
|
||||
let leafIndex = depositEvent ? depositEvent.returnValues.leafIndex.toNumber() : -1
|
||||
|
||||
// Validate that our data is correct
|
||||
const isValidRoot = await mixer.methods.isKnownRoot(await tree.root()).call()
|
||||
const isSpent = await mixer.methods.isSpent(paddedNullifierHash).call()
|
||||
assert(isValidRoot === true, 'Merkle tree assembled incorrectly') // Merkle tree assembled correctly
|
||||
assert(isSpent === false, 'The note is spent') // The note is not spent
|
||||
assert(leafIndex >= 0, 'Our deposit is not present in the tree') // Our deposit is present in the tree
|
||||
|
||||
// Compute merkle proof of our commitment
|
||||
const { root, path_elements, path_index } = await tree.path(leafIndex)
|
||||
|
||||
// Prepare circuit input
|
||||
const input = {
|
||||
// Public snark inputs
|
||||
root: root,
|
||||
nullifierHash,
|
||||
receiver: bigInt(receiver),
|
||||
|
||||
// Private snark inputs
|
||||
nullifier: deposit.nullifier,
|
||||
secret: deposit.secret,
|
||||
pathElements: path_elements,
|
||||
pathIndex: path_index,
|
||||
}
|
||||
|
||||
console.log('Generating SNARK proof')
|
||||
console.time('Proof time')
|
||||
const proof = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, proving_key)
|
||||
const { pi_a, pi_b, pi_c, publicSignals } = websnarkUtils.toSolidityInput(proof)
|
||||
console.timeEnd('Proof time')
|
||||
|
||||
console.log('Submitting withdraw transaction via relayer')
|
||||
|
||||
const account = ephemeral()
|
||||
const HARDCODED_RELAYER_OPTS = {
|
||||
txFee: 90,
|
||||
fixedGasPrice: 22000000001,
|
||||
gasPrice: 22000000001,
|
||||
fixedGasLimit: 5000000,
|
||||
gasLimit: 5000000,
|
||||
verbose: true,
|
||||
}
|
||||
// const provider = new GSNProvider('https://rinkeby.infura.io/v3/c7463beadf2144e68646ff049917b716', { signKey: account })
|
||||
const provider = new GSNDevProvider('http://localhost:8545', { signKey: account, ...HARDCODED_RELAYER_OPTS })
|
||||
web3 = new Web3(provider)
|
||||
const netId = await web3.eth.net.getId()
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
mixer = new web3.eth.Contract(contractJson.abi, contractJson.networks[netId].address)
|
||||
console.log('mixer address', contractJson.networks[netId].address)
|
||||
const tx = await mixer.methods.withdrawViaRelayer(pi_a, pi_b, pi_c, publicSignals).send({ from: account.address, gas: '5000000' })
|
||||
console.log('tx', tx)
|
||||
console.log('Done')
|
||||
}
|
||||
/**
|
||||
* Init web3, contracts, and snark
|
||||
*/
|
||||
let contractJson, erc20ContractJson, erc20mixerJson
|
||||
async function init() {
|
||||
let contractJson, erc20ContractJson, erc20mixerJson
|
||||
if (inBrowser) {
|
||||
// Initialize using injected web3 (Metamask)
|
||||
// To assemble web version run `npm run browserify`
|
||||
@ -207,7 +281,7 @@ async function init() {
|
||||
circuit = await (await fetch('build/circuits/withdraw.json')).json()
|
||||
proving_key = await (await fetch('build/circuits/withdraw_proving_key.bin')).arrayBuffer()
|
||||
MERKLE_TREE_HEIGHT = 16
|
||||
ETH_AMOUNT = 1e18
|
||||
ETH_AMOUNT = '30000000000000000'
|
||||
EMPTY_ELEMENT = 1
|
||||
} else {
|
||||
// Initialize from local node
|
||||
@ -231,9 +305,11 @@ async function init() {
|
||||
mixer.deployedBlock = tx.blockNumber
|
||||
}
|
||||
|
||||
const tx3 = await web3.eth.getTransaction(erc20mixerJson.networks[netId].transactionHash)
|
||||
erc20mixer = new web3.eth.Contract(erc20mixerJson.abi, erc20mixerJson.networks[netId].address)
|
||||
erc20mixer.deployedBlock = tx3.blockNumber
|
||||
if (erc20mixerJson) {
|
||||
const tx3 = await web3.eth.getTransaction(erc20mixerJson.networks[netId].transactionHash)
|
||||
erc20mixer = new web3.eth.Contract(erc20mixerJson.abi, erc20mixerJson.networks[netId].address)
|
||||
erc20mixer.deployedBlock = tx3.blockNumber
|
||||
}
|
||||
|
||||
if(ERC20_TOKEN === '') {
|
||||
erc20 = new web3.eth.Contract(erc20ContractJson.abi, erc20ContractJson.networks[netId].address)
|
||||
@ -273,6 +349,11 @@ if (inBrowser) {
|
||||
const receiver = (await web3.eth.getAccounts())[0]
|
||||
await withdraw(note, receiver)
|
||||
}
|
||||
window.withdrawViaRelayer = async () => {
|
||||
const note = prompt('Enter the note to withdrawViaRelayer')
|
||||
const receiver = (await web3.eth.getAccounts())[0]
|
||||
await withdrawViaRelayer(note, receiver)
|
||||
}
|
||||
init()
|
||||
} else {
|
||||
const args = process.argv.slice(2)
|
||||
@ -320,6 +401,13 @@ if (inBrowser) {
|
||||
else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'withdrawViaRelayer':
|
||||
if (args.length === 3 && /^0x[0-9a-fA-F]{124}$/.test(args[1]) && /^0x[0-9a-fA-F]{40}$/.test(args[2])) {
|
||||
init().then(() => withdrawViaRelayer(args[1], args[2])).then(() => process.exit(0)).catch(err => {console.log(err); process.exit(1)})
|
||||
}
|
||||
else
|
||||
printHelp(1)
|
||||
break
|
||||
case 'auto':
|
||||
if (args.length === 1) {
|
||||
(async () => {
|
||||
|
@ -12,8 +12,10 @@
|
||||
pragma solidity ^0.5.8;
|
||||
|
||||
import "./Mixer.sol";
|
||||
import "@openzeppelin/contracts-ethereum-package/contracts/GSN/GSNRecipient.sol";
|
||||
import "@openzeppelin/contracts-ethereum-package/contracts/GSN/IRelayHub.sol";
|
||||
|
||||
contract ETHMixer is Mixer {
|
||||
contract ETHMixer is Mixer, GSNRecipient {
|
||||
constructor(
|
||||
address _verifier,
|
||||
uint256 _mixDenomination,
|
||||
@ -23,14 +25,77 @@ contract ETHMixer is Mixer {
|
||||
) Mixer(_verifier, _mixDenomination, _merkleTreeHeight, _emptyElement, _operator) public {
|
||||
}
|
||||
|
||||
function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal {
|
||||
_receiver.transfer(mixDenomination - _fee);
|
||||
if (_fee > 0) {
|
||||
_relayer.transfer(_fee);
|
||||
}
|
||||
function _processWithdraw(address payable _receiver) internal {
|
||||
_receiver.transfer(mixDenomination);
|
||||
}
|
||||
|
||||
function _processDeposit() internal {
|
||||
require(msg.value == mixDenomination, "Please send `mixDenomination` ETH along with transaction");
|
||||
}
|
||||
|
||||
function withdrawViaRelayer(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[3] memory input) public {
|
||||
uint256 root = input[0];
|
||||
uint256 nullifierHash = input[1];
|
||||
require(!nullifierHashes[nullifierHash], "The note has been already spent");
|
||||
|
||||
require(isKnownRoot(root), "Cannot find your merkle root"); // Make sure to use a recent one
|
||||
require(verifier.verifyProof(a, b, c, input), "Invalid withdraw proof");
|
||||
nullifierHashes[nullifierHash] = true;
|
||||
// we will process withdraw in postRelayedCall func
|
||||
}
|
||||
|
||||
// gsn related stuff
|
||||
// this func is called by a Relayer via the RelayerHub before sending a tx
|
||||
function acceptRelayedCall(
|
||||
address relay,
|
||||
address from,
|
||||
bytes calldata encodedFunction,
|
||||
uint256 transactionFee,
|
||||
uint256 gasPrice,
|
||||
uint256 gasLimit,
|
||||
uint256 nonce,
|
||||
bytes calldata approvalData,
|
||||
uint256 maxPossibleCharge
|
||||
) external view returns (uint256, bytes memory) {
|
||||
// think of a withdraw dry-run
|
||||
if (_computeCharge(gasLimit, gasPrice, transactionFee) * 2 > mixDenomination) {
|
||||
return (1, "Fee exceeds 50% of transfer value");
|
||||
}
|
||||
|
||||
if (!compareBytesWithSelector(encodedFunction, this.withdrawViaRelayer.selector)) {
|
||||
return (2, "Only withdrawViaRelayer can be called");
|
||||
}
|
||||
|
||||
return _approveRelayedCall();
|
||||
}
|
||||
|
||||
// this func is called by RelayerHub right before calling a target func
|
||||
function preRelayedCall(bytes calldata /*context*/) external returns (bytes32) {}
|
||||
|
||||
event Debug(uint actualCharge, bytes context, address recipient);
|
||||
// this func is called by RelayerHub right after calling a target func
|
||||
function postRelayedCall(bytes memory context, bool /*success*/, uint actualCharge, bytes32 /*preRetVal*/) public {
|
||||
IRelayHub relayHub = IRelayHub(getHubAddr());
|
||||
address payable recipient;
|
||||
assembly {
|
||||
recipient := sload(add(context, 324)) // 4 + (8 * 32) + (32) + (32) == selector + proof + root + nullifier
|
||||
}
|
||||
emit Debug(actualCharge, context, recipient);
|
||||
|
||||
recipient.transfer(mixDenomination - actualCharge);
|
||||
relayHub.depositFor.value(actualCharge)(address(this));
|
||||
// or we can send actualCharge somewhere else...
|
||||
}
|
||||
|
||||
function compareBytesWithSelector(bytes memory data, bytes4 sel) internal pure returns (bool) {
|
||||
return data[0] == sel[0]
|
||||
&& data[1] == sel[1]
|
||||
&& data[2] == sel[2]
|
||||
&& data[3] == sel[3];
|
||||
}
|
||||
|
||||
function withdrawFundsFromHub(uint256 amount, address payable dest) external {
|
||||
require(msg.sender == operator, "unauthorized");
|
||||
IRelayHub(getHubAddr()).withdraw(amount, dest);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ pragma solidity ^0.5.8;
|
||||
import "./MerkleTreeWithHistory.sol";
|
||||
|
||||
contract IVerifier {
|
||||
function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[5] memory input) public returns(bool);
|
||||
function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[3] memory input) public returns(bool);
|
||||
}
|
||||
|
||||
contract Mixer is MerkleTreeWithHistory {
|
||||
@ -29,7 +29,7 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
uint256 public mixDenomination;
|
||||
|
||||
event Deposit(uint256 indexed commitment, uint256 leafIndex, uint256 timestamp);
|
||||
event Withdraw(address to, uint256 nullifierHash, address indexed relayer, uint256 fee);
|
||||
event Withdraw(address to, uint256 nullifierHash, address indexed relayer);
|
||||
|
||||
/**
|
||||
@dev The constructor
|
||||
@ -75,20 +75,17 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
- the receiver of funds
|
||||
- optional fee that goes to the transaction sender (usually a relay)
|
||||
*/
|
||||
function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[5] memory input) public {
|
||||
function withdraw(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[3] memory input) public {
|
||||
uint256 root = input[0];
|
||||
uint256 nullifierHash = input[1];
|
||||
address payable receiver = address(input[2]);
|
||||
address payable relayer = address(input[3]);
|
||||
uint256 fee = input[4];
|
||||
require(fee < mixDenomination, "Fee exceeds transfer value");
|
||||
require(!nullifierHashes[nullifierHash], "The note has been already spent");
|
||||
|
||||
require(isKnownRoot(root), "Cannot find your merkle root"); // Make sure to use a recent one
|
||||
require(verifier.verifyProof(a, b, c, input), "Invalid withdraw proof");
|
||||
nullifierHashes[nullifierHash] = true;
|
||||
_processWithdraw(receiver, relayer, fee);
|
||||
emit Withdraw(receiver, nullifierHash, relayer, fee);
|
||||
_processWithdraw(receiver);
|
||||
emit Withdraw(receiver, nullifierHash, receiver);
|
||||
}
|
||||
|
||||
function toggleDeposits() external {
|
||||
@ -106,6 +103,5 @@ contract Mixer is MerkleTreeWithHistory {
|
||||
}
|
||||
|
||||
function _processDeposit() internal {}
|
||||
function _processWithdraw(address payable _receiver, address payable _relayer, uint256 _fee) internal {}
|
||||
|
||||
function _processWithdraw(address payable _receiver) internal {}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
Make sure your Metamask is unlocked and connected to Kovan (or other network you've deployed your contract to)<br>
|
||||
<a href="#" onclick="deposit()">Deposit</a>
|
||||
<a href="#" onclick="withdraw()">Withdraw</a>
|
||||
<a href="#" onclick="withdrawViaRelayer()">withdrawViaRelayer</a>
|
||||
</p>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
|
@ -13,5 +13,6 @@ module.exports = function(deployer, network, accounts) {
|
||||
await ETHMixer.link(MiMC, miMC.address)
|
||||
const mixer = await deployer.deploy(ETHMixer, verifier.address, ETH_AMOUNT, MERKLE_TREE_HEIGHT, EMPTY_ELEMENT, accounts[0])
|
||||
console.log('ETHMixer\'s address ', mixer.address)
|
||||
const tx = await mixer.initialize()
|
||||
})
|
||||
}
|
||||
|
3609
package-lock.json
generated
3609
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,11 @@
|
||||
"websnark": "git+https://github.com/peppersec/websnark.git#ed6a4d8a6fb081a62af26820980046bbb602d559"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openzeppelin/contracts-ethereum-package": "^2.2.3",
|
||||
"@openzeppelin/gsn-helpers": "^0.2.0",
|
||||
"@openzeppelin/gsn-provider": "^0.1.7",
|
||||
"@openzeppelin/network": "^0.2.9",
|
||||
"@openzeppelin/upgrades": "^2.5.3",
|
||||
"truffle-flattener": "^1.4.0"
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,15 @@ module.exports = {
|
||||
// timeoutBlocks: 200,
|
||||
skipDryRun: true
|
||||
},
|
||||
rinkeby: {
|
||||
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'https://rinkeby.infura.io/v3/c7463beadf2144e68646ff049917b716'),
|
||||
network_id: 4,
|
||||
gas: 6000000,
|
||||
gasPrice: utils.toWei('1', 'gwei'),
|
||||
// confirmations: 0,
|
||||
// timeoutBlocks: 200,
|
||||
skipDryRun: true
|
||||
},
|
||||
mainnet: {
|
||||
provider: () => new HDWalletProvider(process.env.PRIVATE_KEY, 'http://ethereum-rpc.trustwalletapp.com'),
|
||||
network_id: 1,
|
||||
|
Loading…
Reference in New Issue
Block a user