diff --git a/.gitignore b/.gitignore index f0ffdcb..b238f78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +circuits/build + # Created by .ignore support plugin (hsz.mobi) ### Node template # Logs diff --git a/circuits/merkleTree.circom b/circuits/merkleTree.circom index 82a8574..df7b254 100644 --- a/circuits/merkleTree.circom +++ b/circuits/merkleTree.circom @@ -1,6 +1,7 @@ include "../node_modules/circomlib/circuits/bitify.circom"; include "../node_modules/circomlib/circuits/mimcsponge.circom"; +// Computes MiMC(left + right) template HashLeftRight(rounds) { signal input left; signal input right; @@ -15,6 +16,8 @@ template HashLeftRight(rounds) { hash <== hasher.outs[0]; } +// if pathIndex == 0 returns (left = inputElement, right = pathElement) +// if pathIndex == 1 returns (left = pathElement, right = inputElement) template Selector() { signal input inputElement; signal input pathElement; @@ -39,13 +42,14 @@ template Selector() { right <== rightSelector1 + rightSelector2; } +// Verifies that merkle proof is correct for given merkle root and a leaf +// pathIndex input is an array of 0/1 selectors telling whether given pathElement is on the left or right side of merkle path template MerkleTree(levels, rounds) { signal input leaf; + signal input root; signal private input pathElements[levels]; signal private input pathIndex[levels]; - signal output root; - component selectors[levels]; component hashers[levels]; @@ -66,5 +70,5 @@ template MerkleTree(levels, rounds) { selectors[i].inputElement <== hashers[i-1].hash; } - root <== hashers[levels - 1].hash; + root === hashers[levels - 1].hash; } \ No newline at end of file diff --git a/circuits/withdraw.circom b/circuits/withdraw.circom index 4376feb..e6fcc36 100644 --- a/circuits/withdraw.circom +++ b/circuits/withdraw.circom @@ -2,6 +2,7 @@ include "../node_modules/circomlib/circuits/bitify.circom"; include "../node_modules/circomlib/circuits/pedersen.circom"; include "merkleTree.circom"; +// computes Pedersen(nullifier + secret) template CommitmentHasher() { signal input nullifier; signal private input secret; @@ -21,6 +22,7 @@ template CommitmentHasher() { hash <== commitment.out[0]; } +// Verifies that commitment that corresponds to given secret and nullifier is included in the merkle tree of deposits template Withdraw(levels, rounds) { signal input root; signal input nullifier; @@ -36,10 +38,11 @@ template Withdraw(levels, rounds) { component tree = MerkleTree(levels, rounds); tree.leaf <== hasher.hash; - tree.pathElements <== pathElements; - tree.pathIndex <== pathIndex; - - root === tree.root; + tree.root <== root; + for (var i = 0; i < levels; i++) { + tree.pathElements[i] <== pathElements[i]; + tree.pathIndex[i] <== pathIndex[i]; + } // TODO: Check if we need some kind of explicit constraints or something fee === fee; diff --git a/contracts/contracts/Mixer.sol b/contracts/contracts/Mixer.sol index 4e44f1e..6c010aa 100644 --- a/contracts/contracts/Mixer.sol +++ b/contracts/contracts/Mixer.sol @@ -1,15 +1,12 @@ pragma solidity ^0.5.8; import "./MerkleTreeWithHistory.sol"; -import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; contract IVerifier { function verify(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[4] memory input) public returns(bool); } contract Mixer is MerkleTreeWithHistory { - using SafeMath for uint256; - uint256 public transferValue; mapping(uint256 => bool) public nullifiers; IVerifier verifier; @@ -17,17 +14,34 @@ contract Mixer is MerkleTreeWithHistory { event Deposit(address from, uint256 commitment); event Withdraw(address to, uint256 nullifier, uint256 fee); + /** + @dev The constructor + @param _verifier the address of SNARK verifier for this contract + @param _transferValue the value for all deposits in this contract in wei + */ constructor(address _verifier, uint256 _transferValue) MerkleTreeWithHistory(16, 0) public { verifier = IVerifier(_verifier); transferValue = _transferValue; } + /** + @dev Deposit funds into mixer. The caller must send value equal to `transferValue` of this mixer. + @param commitment the note commitment, which is PedersenHash(nullifier + secret) + */ function deposit(uint256 commitment) public payable { require(msg.value == transferValue, "Please send `transferValue` ETH along with transaction"); _insert(commitment); emit Deposit(msg.sender, commitment); } + /** + @dev Withdraw deposit from the mixer. `a`, `b`, and `c` are zkSNARK proof data, and input is an array of circuit public inputs + `input` array consists of: + - merkle root of all deposits in the mixer + - unique deposit nullifier to prevent double spends + - 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[4] memory input) public { uint256 root = input[0]; uint256 nullifier = input[1];