Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8c403b435 | ||
|
|
44b3db0ce4 | ||
|
|
5492acd75c | ||
|
|
55937915c4 | ||
|
|
272c7a94e0 | ||
|
|
21711da0b6 | ||
|
|
76543d068b | ||
|
|
b871d1e49f |
14
README.md
14
README.md
@@ -24,14 +24,10 @@ $ npx hardhat node --fork <https://eth-mainnet.alchemyapi.io/v2/API_KEY> --fork-
|
||||
$ npx hardhat test
|
||||
```
|
||||
|
||||
## Checklist for batch size changing
|
||||
|
||||
find and replace the `CHUNK_TREE_HEIGHT = ` in following files
|
||||
|
||||
1. `circuits/BatchTreeUpdate.circom`
|
||||
2. `contracts/TornadoTrees.sol`
|
||||
3. `tornadoTrees.test.js`
|
||||
|
||||
## build large circuits
|
||||
|
||||
1. docker build . -t tornadocash/tornado-trees
|
||||
Make sure you have enough RAM
|
||||
|
||||
```bash
|
||||
docker build . -t tornadocash/tornado-trees
|
||||
```
|
||||
|
||||
@@ -7,6 +7,7 @@ import "./interfaces/ITornadoTreesV1.sol";
|
||||
import "./interfaces/IBatchTreeUpdateVerifier.sol";
|
||||
import "@openzeppelin/upgrades-core/contracts/Initializable.sol";
|
||||
|
||||
/// @dev This contract holds a merkle tree of all tornado cash deposit and withdrawal events
|
||||
contract TornadoTrees is Initializable {
|
||||
address public immutable governance;
|
||||
bytes32 public depositRoot;
|
||||
@@ -17,7 +18,6 @@ contract TornadoTrees is Initializable {
|
||||
IBatchTreeUpdateVerifier public treeUpdateVerifier;
|
||||
ITornadoTreesV1 public immutable tornadoTreesV1;
|
||||
|
||||
// make sure CHUNK_TREE_HEIGHT has the same value in BatchTreeUpdate.circom
|
||||
uint256 public constant CHUNK_TREE_HEIGHT = 8;
|
||||
uint256 public constant CHUNK_SIZE = 2**CHUNK_TREE_HEIGHT;
|
||||
uint256 public constant ITEM_SIZE = 32 + 20 + 4;
|
||||
@@ -100,48 +100,6 @@ contract TornadoTrees is Initializable {
|
||||
withdrawalsLength = withdrawalsV1Length;
|
||||
}
|
||||
|
||||
// todo make things internal
|
||||
/// @dev There is no array length getter for deposit and withdrawal arrays
|
||||
/// in previous contract, so we have to find them length manually
|
||||
function findArrayLength(
|
||||
ITornadoTreesV1 _tornadoTreesV1,
|
||||
string memory _type,
|
||||
uint256 _from, // most likely array length after the proposal has passed
|
||||
uint256 _step // optimal step size to find first match, approximately equals dispersion
|
||||
) public view returns (uint256) {
|
||||
if (_from == 0 && _step == 0) {
|
||||
return 0; // for tests
|
||||
}
|
||||
// Find the segment with correct array length
|
||||
bool direction = elementExists(_tornadoTreesV1, _type, _from);
|
||||
do {
|
||||
_from = direction ? _from + _step : _from - _step;
|
||||
} while (direction == elementExists(_tornadoTreesV1, _type, _from));
|
||||
uint256 high = direction ? _from : _from + _step;
|
||||
uint256 low = direction ? _from - _step : _from;
|
||||
uint256 mid = (high + low) / 2;
|
||||
|
||||
// Perform a binary search in this segment
|
||||
while (low < mid) {
|
||||
if (elementExists(_tornadoTreesV1, _type, mid)) {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
mid = (low + high) / 2;
|
||||
}
|
||||
return mid + 1;
|
||||
}
|
||||
|
||||
function elementExists(
|
||||
ITornadoTreesV1 _tornadoTreesV1,
|
||||
string memory _type,
|
||||
uint256 index
|
||||
) public view returns (bool success) {
|
||||
// Try to get the element. If it succeeds the array length is higher, it it reverts the length is equal or lower
|
||||
(success, ) = address(_tornadoTreesV1).staticcall{ gas: 2500 }(abi.encodeWithSignature(_type, index));
|
||||
}
|
||||
|
||||
function registerDeposit(address _instance, bytes32 _commitment) public onlyTornadoProxy {
|
||||
uint256 _depositsLength = depositsLength;
|
||||
deposits[_depositsLength] = keccak256(abi.encode(_instance, _commitment, blockNumber()));
|
||||
@@ -167,7 +125,7 @@ contract TornadoTrees is Initializable {
|
||||
uint256 offset = lastProcessedDepositLeaf;
|
||||
require(_newRoot != previousDepositRoot, "Outdated deposit root");
|
||||
require(_currentRoot == depositRoot, "Proposed deposit root is invalid");
|
||||
require(_pathIndices == offset >> CHUNK_TREE_HEIGHT, "Incorrect insert index");
|
||||
require(_pathIndices == offset >> CHUNK_TREE_HEIGHT, "Incorrect deposit insert index");
|
||||
|
||||
bytes memory data = new bytes(BYTES_SIZE);
|
||||
assembly {
|
||||
@@ -206,14 +164,13 @@ contract TornadoTrees is Initializable {
|
||||
bytes32 _argsHash,
|
||||
bytes32 _currentRoot,
|
||||
bytes32 _newRoot,
|
||||
uint256 _pathIndices,
|
||||
uint32 _pathIndices,
|
||||
TreeLeaf[CHUNK_SIZE] calldata _events
|
||||
) public {
|
||||
uint256 offset = lastProcessedWithdrawalLeaf;
|
||||
require(_newRoot != previousWithdrawalRoot, "Outdated withdrawal root");
|
||||
require(_currentRoot == withdrawalRoot, "Proposed withdrawal root is invalid");
|
||||
require(_pathIndices == offset >> CHUNK_TREE_HEIGHT, "Incorrect insert index");
|
||||
require(uint256(_newRoot) < SNARK_FIELD, "Proposed root is out of range");
|
||||
require(_pathIndices == offset >> CHUNK_TREE_HEIGHT, "Incorrect withdrawal insert index");
|
||||
|
||||
bytes memory data = new bytes(BYTES_SIZE);
|
||||
assembly {
|
||||
@@ -226,7 +183,6 @@ contract TornadoTrees is Initializable {
|
||||
bytes32 leafHash = keccak256(abi.encode(instance, hash, blockNumber));
|
||||
bytes32 withdrawal = offset + i >= withdrawalsV1Length ? withdrawals[offset + i] : tornadoTreesV1.withdrawals(offset + i);
|
||||
require(leafHash == withdrawal, "Incorrect withdrawal");
|
||||
require(uint256(hash) < SNARK_FIELD, "Hash out of range");
|
||||
assembly {
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x7c), blockNumber)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x78), instance)
|
||||
@@ -253,6 +209,48 @@ contract TornadoTrees is Initializable {
|
||||
require(_withdrawalRoot == withdrawalRoot || _withdrawalRoot == previousWithdrawalRoot, "Incorrect withdrawal tree root");
|
||||
}
|
||||
|
||||
/// @dev There is no array length getter for deposit and withdrawal arrays
|
||||
/// in the previous contract, so we have to find them length manually.
|
||||
/// Used only during deployment
|
||||
function findArrayLength(
|
||||
ITornadoTreesV1 _tornadoTreesV1,
|
||||
string memory _type,
|
||||
uint256 _from, // most likely array length after the proposal has passed
|
||||
uint256 _step // optimal step size to find first match, approximately equals dispersion
|
||||
) internal view returns (uint256) {
|
||||
if (_from == 0 && _step == 0) {
|
||||
return 0; // for tests
|
||||
}
|
||||
// Find the segment with correct array length
|
||||
bool direction = elementExists(_tornadoTreesV1, _type, _from);
|
||||
do {
|
||||
_from = direction ? _from + _step : _from - _step;
|
||||
} while (direction == elementExists(_tornadoTreesV1, _type, _from));
|
||||
uint256 high = direction ? _from : _from + _step;
|
||||
uint256 low = direction ? _from - _step : _from;
|
||||
uint256 mid = (high + low) / 2;
|
||||
|
||||
// Perform a binary search in this segment
|
||||
while (low < mid) {
|
||||
if (elementExists(_tornadoTreesV1, _type, mid)) {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
mid = (low + high) / 2;
|
||||
}
|
||||
return mid + 1;
|
||||
}
|
||||
|
||||
function elementExists(
|
||||
ITornadoTreesV1 _tornadoTreesV1,
|
||||
string memory _type,
|
||||
uint256 index
|
||||
) public view returns (bool success) {
|
||||
// Try to get the element. If it succeeds the array length is higher, it it reverts the length is equal or lower
|
||||
(success, ) = address(_tornadoTreesV1).staticcall{ gas: 2500 }(abi.encodeWithSignature(_type, index));
|
||||
}
|
||||
|
||||
function getRegisteredDeposits() external view returns (bytes32[] memory _deposits) {
|
||||
uint256 count = depositsLength - lastProcessedDepositLeaf;
|
||||
_deposits = new bytes32[](count);
|
||||
|
||||
@@ -24,11 +24,11 @@ contract Pack {
|
||||
uint256 gasBefore = gasleft();
|
||||
bytes memory data = new bytes(BYTES_SIZE);
|
||||
for (uint256 i = 0; i < CHUNK_SIZE; i++) {
|
||||
(bytes32 hash, address instance, uint32 block) = (hashes[i], instances[i], blocks[i]);
|
||||
(bytes32 _hash, address _instance, uint32 _block) = (hashes[i], instances[i], blocks[i]);
|
||||
assembly {
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x38), block)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x34), instance)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x20), hash)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x38), _block)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x34), _instance)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x20), _hash)
|
||||
}
|
||||
}
|
||||
uint256 gasHash = gasleft();
|
||||
@@ -52,23 +52,23 @@ contract Pack {
|
||||
public
|
||||
view
|
||||
returns (
|
||||
uint256 gas1,
|
||||
uint256 gas2,
|
||||
bytes32 hash
|
||||
uint256,
|
||||
uint256,
|
||||
bytes32
|
||||
)
|
||||
{
|
||||
uint256 gasBefore = gasleft();
|
||||
bytes memory data = new bytes(BYTES_SIZE);
|
||||
for (uint256 i = 0; i < CHUNK_SIZE; i++) {
|
||||
(bytes32 hash, address instance, uint32 block) = (hashes[i], instances[i], blocks[i]);
|
||||
(bytes32 _hash, address _instance, uint32 _block) = (hashes[i], instances[i], blocks[i]);
|
||||
assembly {
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x38), block)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x34), instance)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x20), hash)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x38), _block)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x34), _instance)
|
||||
mstore(add(add(data, mul(ITEM_SIZE, i)), 0x20), _hash)
|
||||
}
|
||||
}
|
||||
uint256 gasHash = gasleft();
|
||||
bytes32 hash = sha256(data);
|
||||
return (gasleft() - gasHash, gasHash - gasBefore, hash);
|
||||
bytes32 hash1 = sha256(data);
|
||||
return (gasleft() - gasHash, gasHash - gasBefore, hash1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,15 @@ contract TornadoTreesMock is TornadoTrees {
|
||||
return currentBlock == 0 ? block.number : currentBlock;
|
||||
}
|
||||
|
||||
function findArrayLengthMock(
|
||||
ITornadoTreesV1 _tornadoTreesV1,
|
||||
string memory _type,
|
||||
uint256 _from,
|
||||
uint256 _step
|
||||
) public view returns (uint256) {
|
||||
return findArrayLength(_tornadoTreesV1, _type, _from, _step);
|
||||
}
|
||||
|
||||
function register(
|
||||
address _instance,
|
||||
bytes32 _commitment,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "tornado-trees",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.10",
|
||||
"main": "src/index.js",
|
||||
"repository": "https://github.com/tornadocash/tornado-trees.git",
|
||||
"author": "Tornadocash team <hello@tornado.cash>",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"src/*",
|
||||
"contracts/*"
|
||||
"contracts/*",
|
||||
"scripts/*"
|
||||
],
|
||||
"scripts": {
|
||||
"compile": "npx hardhat compile",
|
||||
@@ -46,6 +47,7 @@
|
||||
"circomlib": "git+https://github.com/tornadocash/circomlib.git#d20d53411d1bef61f38c99a8b36d5d0cc4836aa1",
|
||||
"dotenv": "^8.2.0",
|
||||
"ffiasm": "^0.1.1",
|
||||
"ffjavascript": "^0.2.35",
|
||||
"fixed-merkle-tree": "^0.5.0",
|
||||
"jssha": "^3.2.0",
|
||||
"snarkjs": "^0.3.57",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const ethers = require('ethers')
|
||||
const BigNumber = ethers.BigNumber
|
||||
const { wtns } = require('snarkjs')
|
||||
const { utils } = require('ffjavascript')
|
||||
|
||||
const { bitsToNumber, toBuffer, toFixedHex, poseidonHash } = require('./utils')
|
||||
|
||||
@@ -32,16 +34,17 @@ function hashInputs(input) {
|
||||
function prove(input, keyBasePath) {
|
||||
return tmp.dir().then(async (dir) => {
|
||||
dir = dir.path
|
||||
fs.writeFileSync(`${dir}/input.json`, JSON.stringify(input, null, 2))
|
||||
let out
|
||||
|
||||
try {
|
||||
if (fs.existsSync(`${keyBasePath}`)) {
|
||||
// native witness calc
|
||||
fs.writeFileSync(`${dir}/input.json`, JSON.stringify(input, null, 2))
|
||||
out = await exec(`${keyBasePath} ${dir}/input.json ${dir}/witness.json`)
|
||||
} else {
|
||||
out = await exec(`npx snarkjs wd ${keyBasePath}.wasm ${dir}/input.json ${dir}/witness.wtns`)
|
||||
out = await exec(`npx snarkjs wej ${dir}/witness.wtns ${dir}/witness.json`)
|
||||
await wtns.calculate(utils.unstringifyBigInts(input), `${keyBasePath}.wasm`, `${dir}/witness.wtns`)
|
||||
const witness = utils.stringifyBigInts(await wtns.exportJson(`${dir}/witness.wtns`))
|
||||
fs.writeFileSync(`${dir}/witness.json`, JSON.stringify(witness, null, 2))
|
||||
}
|
||||
out = await exec(
|
||||
`zkutil prove -c ${keyBasePath}.r1cs -p ${keyBasePath}.params -w ${dir}/witness.json -r ${dir}/proof.json -o ${dir}/public.json`,
|
||||
|
||||
@@ -28,40 +28,70 @@ describe('findArrayLength', () => {
|
||||
})
|
||||
|
||||
it('should work for even array', async () => {
|
||||
const depositsLength = await tornadoTrees.findArrayLength(publicArray.address, 'deposits(uint256)', 4, 2)
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
4,
|
||||
2,
|
||||
)
|
||||
expect(depositsLength).to.be.equal(depositsEven.length)
|
||||
})
|
||||
|
||||
it('should work for empty array', async () => {
|
||||
publicArray = await PublicArray.deploy()
|
||||
// will throw out of gas if you pass non zero params
|
||||
const depositsLength = await tornadoTrees.findArrayLength(publicArray.address, 'deposits(uint256)', 0, 0)
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
0,
|
||||
0,
|
||||
)
|
||||
expect(depositsLength).to.be.equal(0)
|
||||
})
|
||||
|
||||
it('should work for odd array', async () => {
|
||||
publicArray = await PublicArray.deploy()
|
||||
await publicArray.setDeposits(depositsOdd)
|
||||
const depositsLength = await tornadoTrees.findArrayLength(publicArray.address, 'deposits(uint256)', 4, 2)
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
4,
|
||||
2,
|
||||
)
|
||||
expect(depositsLength).to.be.equal(depositsOdd.length)
|
||||
})
|
||||
|
||||
it('should work for even array and odd step', async () => {
|
||||
const depositsLength = await tornadoTrees.findArrayLength(publicArray.address, 'deposits(uint256)', 4, 3)
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
4,
|
||||
3,
|
||||
)
|
||||
expect(depositsLength).to.be.equal(depositsEven.length)
|
||||
})
|
||||
|
||||
it('should work for odd array and odd step', async () => {
|
||||
publicArray = await PublicArray.deploy()
|
||||
await publicArray.setDeposits(depositsOdd)
|
||||
const depositsLength = await tornadoTrees.findArrayLength(publicArray.address, 'deposits(uint256)', 4, 3)
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
4,
|
||||
3,
|
||||
)
|
||||
expect(depositsLength).to.be.equal(depositsOdd.length)
|
||||
})
|
||||
|
||||
it('should work for odd array and step 1', async () => {
|
||||
publicArray = await PublicArray.deploy()
|
||||
await publicArray.setDeposits(depositsOdd)
|
||||
const depositsLength = await tornadoTrees.findArrayLength(publicArray.address, 'deposits(uint256)', 4, 1)
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
4,
|
||||
1,
|
||||
)
|
||||
expect(depositsLength).to.be.equal(depositsOdd.length)
|
||||
})
|
||||
|
||||
@@ -69,7 +99,7 @@ describe('findArrayLength', () => {
|
||||
const deposits = Array.from(Array(100).keys())
|
||||
publicArray = await PublicArray.deploy()
|
||||
await publicArray.setDeposits(deposits)
|
||||
const depositsLength = await tornadoTrees.findArrayLength(
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
67,
|
||||
@@ -82,7 +112,12 @@ describe('findArrayLength', () => {
|
||||
const deposits = Array.from(Array(30).keys())
|
||||
publicArray = await PublicArray.deploy()
|
||||
await publicArray.setDeposits(deposits)
|
||||
const depositsLength = await tornadoTrees.findArrayLength(publicArray.address, 'deposits(uint256)', 1, 50)
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
1,
|
||||
50,
|
||||
)
|
||||
expect(depositsLength).to.be.equal(deposits.length)
|
||||
})
|
||||
|
||||
@@ -100,7 +135,7 @@ describe('findArrayLength', () => {
|
||||
const deposits = Array.from(Array(len).keys())
|
||||
publicArray = await PublicArray.deploy()
|
||||
await publicArray.setDeposits(deposits)
|
||||
const depositsLength = await tornadoTrees.findArrayLength(
|
||||
const depositsLength = await tornadoTrees.findArrayLengthMock(
|
||||
publicArray.address,
|
||||
'deposits(uint256)',
|
||||
days * depositsPerDay,
|
||||
|
||||
14
yarn.lock
14
yarn.lock
@@ -3890,6 +3890,15 @@ ffjavascript@0.2.34, ffjavascript@^0.2.30:
|
||||
wasmcurves "0.0.14"
|
||||
worker-threads "^1.0.0"
|
||||
|
||||
ffjavascript@^0.2.35:
|
||||
version "0.2.35"
|
||||
resolved "https://registry.yarnpkg.com/ffjavascript/-/ffjavascript-0.2.35.tgz#9166d95173b1c0a743b455bb03a72b581922a42e"
|
||||
integrity sha512-xnC51tWbi0ah4SH+02jEfJyO+P+NiZWnxQrLDLtBYY1Dv3QM5ydxzd+gxnLEfWdT8i1bMM5pIh5P25l6fNCaVQ==
|
||||
dependencies:
|
||||
big-integer "^1.6.48"
|
||||
wasmcurves "0.0.14"
|
||||
web-worker "^1.0.0"
|
||||
|
||||
ffwasm@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/ffwasm/-/ffwasm-0.0.7.tgz#23bb9a3537ecc87c0f24fcfb3a9ddd0e86855fff"
|
||||
@@ -8352,6 +8361,11 @@ wasmcurves@0.0.5:
|
||||
big-integer "^1.6.42"
|
||||
blakejs "^1.1.0"
|
||||
|
||||
web-worker@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.0.0.tgz#c7ced4e1eb6227636ada35056a9e5a477414e4d0"
|
||||
integrity sha512-BzuMqeKVkKKwHV6tJuwePFcxYMxvC97D448mXTgh/CxXAB4sRtoV26gRPN+JDxsXRR7QZyioMV9O6NzQaASf7Q==
|
||||
|
||||
web3-bzz@1.2.11:
|
||||
version "1.2.11"
|
||||
resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.11.tgz#41bc19a77444bd5365744596d778b811880f707f"
|
||||
|
||||
Reference in New Issue
Block a user