The Zellic cryptography team (Malte Leip, Mohit Sharma, and Avi Weinstock) and Sampriti Panda participated in the most recent ZK Hack competition, ZK Hack IV↗, consisting of three puzzles overall, with points determined by how fast one managed to solve it. We were happy to win the first puzzle and place second in the third one, securing a second place overall.

This ZK Hack’s puzzles consisted of small cryptographic applications written in Rust using the arkworks libraries↗. They combined the kind of cryptographic primitives that are typically used in projects surrounding ZK, but in each puzzle some kind of vulnerability was introduced as well. The task was then to understand the provided code, find the vulnerability theoretically, and then implement a solution leveraging the found vulnerability.
Puzzle 1: Gamma Ray
In the first puzzle, we were presented with parts of a small Zcash clone, the task being to double spend a note. Double spending is prevented in Zcash with the use of nullifiers; to spend a note, you must reveal a nullifier associated with that note, and this nullifier is then marked as used. Already used nullifiers are not allowed to be used anymore, so this prevents double spending as long as there is only one valid nullifier that can be computed for each note.
In the case of this puzzle, however, the relationship was roughly and , where is a point on an elliptic curve and refers to the x-coordinate in affine coordinates. But if is a point in affine coordinates on this elliptic curve, then it holds that and thus has the same x-coordinate. Thus the note does not change if we replace by (taken modulo the order of ), but the hash changes, so we obtain a second nullifier associated with the note. A more detailed write-up can be found on the ZK Hack page for this puzzle↗.
This was our solution code:
let secret_hack = MNT4BigFr::from(MNT6BigFr::MODULUS) - MNT4BigFr::from(leaked_secret);
let nullifier_hack = <LeafH as CRHScheme>::evaluate(&leaf_crh_params, vec![secret_hack]).unwrap();
Puzzle 2: Supervillain
The second puzzle concerned a combination of aggregated BLS signatures with a simple proof-of-knowledge scheme. Both of those used a pairing . We recently published an introductory article on pairings↗, so head over there if you would like an introduction.
In a sequence of signatures for a fixed hashed message , signature number is signed with a public key of the form , with a fixed element of . The signature is a normal BLS signature, so . Those can be aggregated with , and then verified by checking . That this equality follows from , and that in turn these equalities hold, is a consequence of the bilinearity of .
To prove that the signer actually knew , they can provide another elliptic curve element , and verification checks that , where is a fixed element.
In the puzzle, we were then given a sequence of public keys and proofs and for which verification of the proof of knowledge of succeeds (we do not get though). Our task was to come up with and , so that the proof-of-knowledge verification succeeds for this pair, and additionally a signature that would verify as a BLS signature for a specific hashed message using the aggregate key . This means our constraints were
and
and we have available for . The available equalities are usable for the first constraint but not the second, so to make the second one pass, we choose and , making both sides 0 by bilinearity1. The left hand side of the first constraint can then be rewritten as follows.