- Original document: https://hackmd.io/Bq588JDoSSWFyiKmfUjbdA?view
- Writer: Igor Gulamov
[TOC]
Igor Gulamov conducted the audit of zkopru smart contracts and circuits.
This review was performed by an independent reviewer under fixed rate.
Smart contracts at https://github.com/zkopru-network/zkopru/tree/audit-v2/packages/contracts and circuits at https://github.com/zkopru-network/zkopru/tree/audit-v2/packages/circuits.
We find 1 major and 1 critical issues. These issues are fixed.
We consider commit 7020a0136bf0f8fc4e521bc76615bdfa62691a7d as a safe version from the informational security point of view.
Anybody can claim the balance from the auction.
Fixed at bd4c08afaf4f3966e4d1b3a15f059176f003c885
Node
We propose replacing emptyNode
with lastNotEmptyNode
.
uint256 lastNotEmptyNode = treeSize + leaves.length - 1;
if (nodeIndex <= lastNotEmptyNode) {
lastNotEmptyNode >>= 1;
Fixed at c3a447c5ba18ad06e94a05892bb3b5f829487a01
EdDSAPoseidonVerifier
is assuming, that
Will not fix
Checks will be implemented on client side.
2. packages/circuits/lib/zk_transaction.circom#L214 packages/contracts/contracts/zkopru/controllers/UserInteractable.sol#L24
Limits are inconsistent on contract and circuit sides. We propose using the same limits on both sides.
Fixed at 97591efae6312e575207e2919e66037f0c3e8491
Valid proofs could be easily forged for zero-valued VK. We propose adding a check, that the value is not default, or replacing default values with randomly generated values.
Will not fix
We'll assume that the VKs will be stored correctly during its initialization process.
If it's not stored correctly, users should not use that contract.
Unoptimized code. We propose use the following equality to reduce number of mul gates.
Will not fix
Unoptimized code. We propose rewriting it as
component sum[n];
var lc = 0;
for(var i = 0; i < n; i++) {
sum[i] = IfElseThen(1);
sum[i].obj1[0] <== addr;
sum[i].obj2[0] <== note_addr[i];
sum[i].if_v <== note_amount[i];
sum[i].else_v <== 0;
lc += sum[i].out;
}
out <== lc;
Will not fix
2nd Mux1
component is unnecessary. We propose rewriting it as
left[level] = Mux1();
left[level].c[0] <== nodes[level];
left[level].c[1] <== siblings[level];
left[level].s <== path_bits.out[level];
branch_nodes[level].left <== left[level].out;
branch_nodes[level].right <== nodes[level] + siblings[level] - branch_nodes[level].left;
Will not fix
Suboptimal circuit. We propose rewriting it as
typeof_new_note_is_zero[i] = IsZero();
typeof_new_note_is_zero[i].in <== typeof_new_note[i];
revealed_token_addr[i] <== new_note_token_addr[i] * (1-typeof_new_note_is_zero[i].out);
revealed_erc20_amount[i] <== new_note_erc20[i] * (1-typeof_new_note_is_zero[i].out);
revealed_erc721_id[i] <== new_note_erc721[i] * (1-typeof_new_note_is_zero[i].out);
Will not fix
Unused variable range_limit
.
Will not fix
Unnecessary checks. In-field and on curve checks are applied inside precompiled contracts. SNARK_SCALAR_FIELD
checks are enough for the Groth16 verifier.
Will not fix
We propose initialize vkX
with value from vk.ic[0]
.
Will not fix
Only some of Block
fields are used. We propose optimizing data loading to the memory. Loading the whole block into the memory is suboptimal.
Will not fix
For example, RANGE_LIMIT
, so, it's enough to serialize it into 28 bytes instead of 32 bytes.
We propose to flatten NFT address and id into one key and use it as ERC20 address with totalSupply=1
.
Input and output sums check is suboptimal. We propose rewriting it as one array with positive inputs and negative outputs and check, that the total sum is zero for each asset type.
Also, you may skip ETH check, just check all sums of all notes (without taking into account the asset type).
packages/contracts/contracts/zkopru/storage/Storage.sol#L9
packages/contracts/contracts/zkopru/storage/Reader.sol#L10
Unnecessary import.
160 bits are enough for collision resistance.
packages/contracts/contracts/zkopru/libraries/Deserializer.sol#L203
packages/contracts/contracts/zkopru/libraries/Deserializer.sol#L254
packages/contracts/contracts/zkopru/libraries/Deserializer.sol#L331
packages/contracts/contracts/zkopru/libraries/Deserializer.sol#L356
packages/contracts/contracts/zkopru/libraries/Deserializer.sol#L383
packages/contracts/contracts/zkopru/libraries/Deserializer.sol#L512
packages/contracts/contracts/zkopru/libraries/Deserializer.sol#L565
packages/contracts/contracts/zkopru/libraries/Deserializer.sol#L633
free_mem
pointer grows, but this memory will be never used in future. We propose fixing the memory leak.
packages/contracts/contracts/zkopru/controllers/validators/MigrationValidator.sol#L169
Usage of in-memory struct is unnecessary here. We recommend replacing it with bytes32
.
packages/contracts/contracts/zkopru/controllers/validators/WithdrawalTreeValidator.sol#L51-L54
packages/contracts/contracts/zkopru/libraries/MerkleTree.sol#L184-L186
We propose rewriting it as
uint256 res = (a + b - 1) / b;
packages/contracts/contracts/zkopru/libraries/MerkleTree.sol#L117-L128
We propose renaming nextSiblings
and adding description, how it works on zero nodes.