Audit finding: Users can use any previously seen Merkle root
Project: MantaNetwork
Auditor: Veridise
Location: pallets/manta-pay/src/lib.rs
Impact: Medium
Vulnerability class (audit): Hash Collision
Vulnerability class (Scout): Dependency
Status: Acknowledged

4.1.2 V-MANC-VUL-002: Users can use any previously seen Merkle root

Severity: Medium
Type: Hash Collision
Commit: 45ba60e1d
Status: Acknowledged

File(s): pallets/manta-pay/src/lib.rs
Location(s): has_matching_utxo_accumulator_output

The MantaPay protocol maintains a Merkle tree on the ledger where the leaves of the ledger are the hashes of the UTXOs generated during the protocol’s lifetime. In order to spend a UTXO, users must supply a ZK proof that the UTXO belongs to the Merkle tree on the ledger. The membership proof takes as input the root of the Merkle tree (public input), the inner node hashes (private inputs), and proves that the root can be derived from the inner node hashes and leaf.

Ideally, the ledger would check that the root provided is equal to the latest root on-chain. However, this isn’t done in practice as the transaction could easily be front-runned since every transaction changes the root. Instead, the ledger maintains a set of all previously generated roots and just checks that the root provided belongs to that set.

However, by allowing the root provided by the user to be any previously generated root, an attacker simply needs to find a hash collision with any previously generated root to steal assets. The likelihood of finding a collision grows quadratically with the number of previously seen hashes. In particular, given an output size of b bits and n previously generated hashes, the likelihood of finding a collision with any of the n hashes is approximately n^2/2^(b+1).

The current version of the Protocol uses the Poseidon hash function, which produces 255-bit hashes and, in theory, should be safe even with billions of previously seen roots. However, this is contingent on the safety of the Poseidon hash. While there has been a significant amount of research and analysis conducted on the function, including various attacks and optimizations, there is no formal proof of its security and correctness, let alone any proofs about concrete implementations.

Impact: Storing all previously seen roots significantly increases the likelihood of collisions. If any attack or weakness is found in the Poseidon hash, this can be an additional means of attacking the protocol.

Recommendation: There are a few ways to mitigate this. Protocols like Semaphore maintain a timeout period TIMEOUT and associate each root with a timestamp indicating when it was created. Any root created before now() - TIMEOUT is rejected. Another option is to only store the N previously generated roots and only allow a root if it belongs to the set of N previously generated roots. The latter option would have the additional benefit of not needing to store every root on-chain.

Developer Response: The developers acknowledged the issue and will either use a timestamp or only maintain the N previously generated roots.

Summary: Allowing any previously seen Merkle root increases the risk of hash collisions, potentially allowing asset theft.