-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
de7b1b1
commit 0383bcd
Showing
1 changed file
with
60 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# Introduction | ||
The 'Tornado Crash' challenge, rooted in the [Miximus](https://github.com/barryWhiteHat/miximus/tree/e774ef8524ba947285c671b946ce56f64b48c116) codebase, presented a unique vulnerability not in its cryptographic circuit but within the verifier contract. | ||
|
||
# Background | ||
In Miximus, a crucial component is using nullifiers for withdrawal processes. A nullifier is a key element designed to prevent double-spending or duplicate actions within blockchain transactions. When a user initiates a withdrawal, they are required to post a nullifier on-chain. This nullifier acts as a unique identifier for the transaction. If the nullifier has already been recorded on-chain, any subsequent transactions using the same nullifier are automatically rejected, thereby ensuring the integrity of the withdrawal process. | ||
|
||
# Technical Details | ||
The vulnerability in Miximus stemmed from a mismatch in data representation. The EVM handles numbers up to 256 bits, while the SNARK scalar field, integral to the cryptographic processes of Miximus, has a 254-bit limit (babyjubjub `p = 21888242871839275222246405745257275088548364400416034343698204186575808495617`). This discrepancy led to an overflow issue. | ||
|
||
For instance, considering p as the SNARK scalar field order, any number x during the proof generation would be reduced to x % p. Consequently, a number like p + 1 would be reduced to 1. | ||
|
||
## The Vulnerability | ||
The core of the issue in the 'Tornado Crash' challenge was that the smart contract responsible for verifying the uniqueness of nullifiers did not check whether the nullifier was within the bounds of the SNARK scalar field. This oversight meant that if a user submitted a nullifier x greater than or equal to p, it could effectively be used twice: once as x and once as x % p. Both forms would be valid within the circuit and generate successful proofs. | ||
|
||
This vulnerability allowed users to exploit the system by claiming the same transaction or action twice with two different yet technically valid nullifiers. When a user submitted a withdrawal with a nullifier x, since x had not been seen before, it was considered valid. Subsequently, the same withdrawal could be claimed with x % p, which, to the contract, appeared as a different, unseen nullifier, thus also passing validation. | ||
|
||
In addition to submitting a nullifier x greater than or equal to p, users could also submit a nullifier less than p. This initially seems within the normal bounds of the system. However, the exploit was in adding p to this smaller nullifier, effectively creating a new, larger number which, when reduced modulo p, resulted in a different valid nullifier. | ||
|
||
For instance, with a nullifier y such that y < p, the user could submit y and then submit y + p. Though y + p is technically a different number, when processed within the circuit, it reduces to y, due to the modulo operation. Therefore, the smart contract viewed y and y + p as two separate nullifiers, allowing the user to claim or execute the same action twice, despite technically using the same original nullifier in different mathematical forms. | ||
|
||
# Solution | ||
|
||
## Witness Generation | ||
After making a deposit, participants could generate a witness using the following command: | ||
|
||
```python | ||
pk = genWitness(miximus, nullifier, sk, NUMBER_OF_DEPOSIT, tree_depth, fee, "../zksnark_element/pk.raw") | ||
``` | ||
|
||
In this command, NUMBER_OF_DEPOSIT represents the global position of your deposit transaction. | ||
|
||
The result of running this command would produce an output similar to: | ||
|
||
``` | ||
"pk": { | ||
"a": [...], | ||
"a_p": [...], | ||
"b": [...], | ||
"b_p": [...], | ||
"c": [...], | ||
"c_p": [...], | ||
"h": [...], | ||
"k": [...], | ||
"input": [ | ||
..., | ||
..., | ||
45826975940739001712875990737140574885322668875248592052086356330207943334071, | ||
..., | ||
... | ||
] | ||
} | ||
``` | ||
In this output, the crucial element is `input[2]`, which in this case is `45826975940739001712875990737140574885322668875248592052086356330207943334071`. This value represents the nullifier. | ||
|
||
## Constructing a New Valid Proof | ||
By adding p (the SNARK scalar field order) to `input[2]`, a new nullifier could be constructed. This new nullifier, when submitted to the blockchain, would be distinct enough to bypass the smart contract's validation check, despite being derived from the same original nullifier. This exploit could be used to generate multiple valid proofs for the same deposit, effectively allowing participants to drain the pool. | ||
|
||
# See also | ||
1. [a16z ZkDrops: Missing Nullifier Range Check](https://github.com/0xPARC/zk-bug-tracker#9-a16z-zkdrops-missing-nullifier-range-check) | ||
2. [EY Nightfall: Missing Nullifier Range Check](https://github.com/0xPARC/zk-bug-tracker#18-ey-nightfall-missing-nullifier-range-check) |