Skip to content

Latest commit

 

History

History
245 lines (138 loc) · 13.1 KB

AUDIT-REPORT.md

File metadata and controls

245 lines (138 loc) · 13.1 KB

zkopru audit

$$ \DeclareMathOperator*{\amper}{&} $$

[TOC]

Introduction

Igor Gulamov conducted the audit of zkopru smart contracts and circuits.

This review was performed by an independent reviewer under fixed rate.

Scope

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.

Issues

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.

Critical

Anybody can claim the balance from the auction.

Fixed at bd4c08afaf4f3966e4d1b3a15f059176f003c885

Major

Node $110$ is not empty at the example, but it will be computed as empty at $\text{level}=1$.

We propose replacing emptyNode with lastNotEmptyNode.

uint256 lastNotEmptyNode = treeSize + leaves.length - 1;
if (nodeIndex <= lastNotEmptyNode) {
lastNotEmptyNode >>= 1;

Fixed at c3a447c5ba18ad06e94a05892bb3b5f829487a01

Warnings

EdDSAPoseidonVerifier is assuming, that $A$ point is on the curve. Also, we recommend adding on curve checks for $R8$.

Will not fix

Checks will be implemented on client side.

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.

$$ \amper\limits_{i=1}^n x_i = \text{is_zero}\left(n - \sum\limits_{i=1}^n \text{int}(x_i)\right) $$

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

Comments

1. Suboptimal $\mathcal{W}$ serialization

For example, $\mathcal{N}.\mathsf{v_{eth}}$ is limited by RANGE_LIMIT, so, it's enough to serialize it into 28 bytes instead of 32 bytes.

2. Representing NFTs as partial cases at the circuit side could be unnecessarily

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.