With the rise of privacy cryptocurrencies like Zcash, Monero, etc., I started to learn about how the privacy element can be applied in digital payment and, more importantly, the underlying technologies around these. I sincerely believe that these technologies can be applied to a greater extent beyond payment scenarios.
zk-SNARKs is not new — the related papers and concepts have been there for decades. Moreover, its variants, such as bulletproofs, zk-STARKs, Sonic, etc, have raised recently in order to address the shortcomings of vanilla zk-SNARKs, particularly the trusted setup.
There are quite a number of useful articles around that talk about the technical (and mathematical) aspect of zero-knowledge proofs and SNARKs. I would recommend the technical introduction in Zcash website as well as the blog posts by Vitalik Buterin on Quadratic Arithmetic Programs and zk-SNARKs.
zkp.science has put together a good summary of zero-knowledge proving systems, including the history and the proving system implementation available. This article focuses on using the toolbox and/or libraries to construct the circuits for zk-SNARKs, namely ZoKrates and snarkjs/circom.
General zk-SNARKs Proving System Workflow
In a nutshell, the use of zk-SNARKs proving system usually involves 2 phases, namely one-off compilation and runtime proving & verification:

Compilation composes of the arithmetic circuit construction from the source code (e.g. circom language) as well as the setup to generate proving and verification keys. It’s one time as the generated keys can be used by the provers and verifiers repeatedly for the same circuit, e.g. certain rules in online games.
At runtime, provers can start generating proofs to prove the verifiers that they not only know the secret inputs (e.g. pre-image), but also are able to perform the required computation based on the public signals (could be inputs and/or outputs) — the step that is known as “witness calculation”. At the other side, verifiers take the given proofs and the public signals and confirm that they are valid or not.
The beauty here is that i) the verifiers do not need to have prior or extra knowledge to verify such computation (a.k.a. zero-knowledge), and ii) there is no transfer of secret inputs (or, in other words, information leakage) between provers and verifiers.
ZoKrates and snarkjs/circom
For those who are familiar in developing Ethereum smart contracts and DApps, you may probably have heard ZoKrates before. It’s a toolbox to build zk-SNARKs and enable verification via Ethereum smart contracts generated.
Applying the general workflow discussed previously, here is how it’s like for ZoKrates:

The best thing about ZoKrates, from my point of view, is its language to build circuits — it should be fairly similar to Python scripting language. Moreover, it’s also well developed and maintained. In addition, there are relevant enough resources (e.g. youtube videos in crypto related conferences, workshops in hackathons) to get beginners started.
Another one to introduce is snarkjs/circom. And here is the equivalent workflow:

circom aligns more with the “circuit” concept, where the experience is more like connecting inputs and outputs bit-by-bit, rather than writing “programs”. The other selling point is that snarkjs allows generating proofs in JavaScript, so this can be used for use cases where the DApp itself needs to generate proofs right from the web client. Like ZoKrates, it’s also able to generate Ethereum smart contracts.
Multiple Provers and Verifiers
You may have noticed I use plurals for provers and verifiers. One key reminder when building zk-SNARKs is that the verification is not just between 1 prover and 1 verifier. It’s possible that the proof can be obtained from other transactions and is then reused. To address this, we should add some form of unique identification, such as hash of pre-image, Ethereum address, session ID, etc.
Hashing: SHA256 and Pedersen
One of the most common, readily accessible cryptographic hashing algorithm is SHA256. In addition to its existence as libraries in usual programming languages / platforms, it also comes handy in circuit construction, such as the standard library in ZoKrates and circomlib.
However, SHA256 is very expensive in zk-SNARKs circuit — most likely results in higher number of wires and constraints. On the other hand, Pedersen hash is considered to be more zk-SNARKs-friendly. It has been used in Zcash Sapling implementation for its circuits — see more in its specification in zips and also the blog here.
I also built a sample circuit with 1 hash action using SHA256 versus Pedersen implementations respectively, and here is what I got:
SHA256 Hash (hashes/sha256/512bitPacked.code):
Number of constraints: 75380
Pedersen Hash (hashes/pedersen/6bit.code):
Number of constraints: 18412
It’s roughly~76% reduction. And I got similar reduction in snarkjs/circom equivalent.
Note: circom may run out of heap memory when compiling SHA256 hashing. An addition parameter (max_old_space_size) may need to be added in circom script:
#!/usr/bin/env node --max_old_space_size=4096
Conclusion
This is my first article to talk about zero knowledge proofs. Hopefully I can share more in detail on my experience in using these in practice.
Happy circuit building and proving!
