Skip to main content
Table of contents
Cayden Liao

Intro to Zero-Knowledge: A Guided Tour of the ZK Landscape, Part 2

An intro to Zero-Knowledge and applications of zero-knowledge including ZK payments, ZK hardware acceleration, and zkVMs
Article heading

Introduction

Last time, we explored the fundamentals of zkSNARKS and their applications in blockchains. We introduced the following:

  • zkEVMs and how they increase transaction speed and scalability on the Ethereum network
  • zkBridges and how they enable secure exchanges between different cryptocurrencies
  • ZK programming languages and how they simplify the development of ZK circuits for software engineers

For a refresher on these aspects of the ZK landscape, see Part 1 here.

Having laid the foundation of the ZK landscape, let’s take a look at a few more applications of zero-knowledge: ZK payments, ZK hardware acceleration, and zkVMs. With these applications in mind, we’ll have taken a brief look at some of the most notable areas of the landscape and arrived at the end of our tour.

As a brief overview of ZK payments, we’ll cover an introduction to fully private and secure transactions. For ZK hardware acceleration, we’ll cover how zkSNARKs are used to speed up cryptographic operations. Lastly, for zkVMs, we’ll cover how zkSNARKs are used to increase the speed of any blockchain network, not just Ethereum.

ZK Payments

Bitcoin, Ethereum, and Solana are all public cryptocurrencies, which means that all transactions, accounts, and data are public. However, in some cases, private transactions are necessary. ZK payments aim to use ZK proofs to ensure three criteria:

  1. Private transaction amounts. The amount of money transferred between accounts must be hidden to other users. Only the sender and the receiver know how many coins were transferred.
  2. Private user identities. Traditional public blockchains often expose the addresses of the sender and receiver. On the other hand, ZK payments ensure the privacy of the addresses in public.
  3. Private transaction history. Information such as transaction history and account balance is kept hidden from the public blockchain. While data is still being uploaded to the main chain, sensitive information is kept secret.

On the surface level, public blockchains seem to be private. Take for example this Ethereum address. Does this look like a person’s identity?

0xB256227cfc6209fCC7bA1c4acb9329721Acb89d4

Although it might not look like it, the person’s transactions and activities are public. These bits of information allow for others to trace a person’s identity and be able to fairly quickly monitor their blockchain usage at all times.

The bulk of ZK payments is made through designs based on Zcash. In a nutshell, Zcash allows users to transfer coins on the Zcash blockchain, giving users the choice of keeping their transactions public or private.

Transactions on Zcash use a UTXO structure similar to Bitcoin’s. When a sender processes a transaction, the sender posts two notes. One note contains the sender’s new balance, while the other contains the amount of money being sent. To stay private, these notes are encrypted with ChaCha20-Poly1305. ChaCha20 is a symmetric encryption algorithm similar to AES and was chosen in this instance for its fast encryption speed. Only the sender and receiver can decrypt the notes with their own set of keys. The two new encrypted notes are then hashed and committed into a central Merkle tree.

Up to this point, the public is unable to verify the transaction since the notes are encrypted. This is where ZKPs come in. Older versions of Zcash, before the release of Sapling activation in 2018, used BCTV14, a slower, older, more energy-intensive proving system than Groth16. Given that Groth16 was created in 2016, Zcash stuck with BCTV14 to ensure the safety of Groth16 before implementing Sapling activation two years later. With Sapling activation, Groth16 is chosen for its fast proving/generating speeds. This allows others to verify the validity of the private transaction without revealing any data on the users themselves.

(As a fun fact, these proving systems are both named after the research papers that originally proposed them. BCTV14 was published in 2014 by authors whose names start with B, C, T, and V. The Groth16 paper was published in 2016 by Jens Groth.)

Zcash specifically uses two ZK circuits and a value-commitment check:

  1. Spend circuit. The spend statement is a ZK proof that is in charge of making sure a transaction is able to go through correctly. This circuit creates a proof that confirms to a verifier that the note and Merkle path is valid, the original spender is authorized to perform this transaction, the previous note is nullified, and the balances on the two new notes add up to the original note.

  2. Output circuit. The output statement is a ZK proof in the creation of new coins. It allows verifiers to confirm that the note created for the coin is valid along with the value commitment.

  3. (Homomorphic) Pedersen commitment. The Pedersen commitment, used by Zcash (Zcash Protocol Specification, p. 18), is used whenever a value is committed. The homomorphic property allows Pedersen commits to be added and subtracted. For example, to ensure coins aren’t created/destroyed during a transaction, the value commit to the sender’s new balance can be added to the value commit to the sent coins and checked to ensure the total amount of coins stays the same.

ZK Hardware Acceleration

Our next stop is on accelerating ZKP generation. Currently, generating ZKPs for transactions is a time- and resource-consuming process. However, work is in progress to speed this up using specialized hardware. Proof generation has gone from the usage of CPUs to now GPUs and even specially designed circuits on FPGAs.

CPUs are slow. They are used for everyday computation that isn’t fit for math-intensive ZK proof generation. People gradually moved to use GPUs since graphic cards were designed to perform special-purpose, vectorized calculations at much faster speeds (e.g., gaming and high—frame rate video streaming). However, GPUs were still designed for a wide variety of uses. The community began to explore solutions leveraging FPGAs, which are even more specialized.

FPGAs are pieces of hardware that can be bought off the shelf. Users can reconfigure the circuits inside to fit specific circuit requirements. Rather than having dedicated sections of GPUs/CPUs for everyday computer calculations, the entire FPGA circuitry can be changed and dedicated to ZK. The most time-consuming parts of proof generation come from two categories.

  1. (Fast) Fourier transforms (FFTs). A ZKP typically uses Fourier transforms to perform division over an elliptic pairing. Although FFTs run in subexponential time, they are still the bulk of a ZKP generation.

  2. Multiplications. Most ZKPs consist of elliptic curve multiplications, which are computationally expensive. Since a single gate cannot express an elliptic operation, multiple binary gates must be stringed together in order to perform elliptic operations.

To address these challenges and speed up the proof-generation process, a solution can be found through the development of application-specific integrated circuits (ASICs), specialized pieces of hardware specifically built for the purpose of ZKP generation. By hardwiring the required mathematical operations into hardware, ASICs can perform these computations much more efficiently and in parallel, dramatically reducing the time taken to generate ZKPs.

The use of ASICs for ZKP generation offers several advantages, including increased speed, reduced power consumption, and improved scalability. These specialized hardware solutions can optimize the most time-consuming operations, allowing zkSNARKs to be more practical for high-performance applications, such as privacy-preserving transactions, secure communications, and verifiable computations.

However, using ASICs comes with drawbacks as well. ASICs are best suited for mature applications that rarely change, like SHA-256 POW mining. On the other hand, ZKP generation continues to evolve rapidly. Designing custom ASICs for ZKPs requires significant resources, and it’s essential to ensure the security of the hardware to prevent potential attacks. Additionally, unlike FPGAs, once an ASIC is hardwired, it is permanently ingrained in the chip. If the ZKP algorithm is changed or an update is needed, a whole new ASIC must be used and the original would be useless. Not to mention the tape-out costs of creating ASICs, typically ranging from 2to2 to 3 million. Thus, the development costs of ASICs have made them prohibitive for ZKPs so far.

One example is Cysic, whose approach is based on FPGAs and ASICs. Customers submit the ZK circuit they seek to accelerate, and Cysic builds it for them. Without relying on GPUs, FPGAs and ASICs are much faster, resulting in less time and resources during proof generation.

zkVMs

Now let’s travel to our last stop on ZK virtual machines (zkVMs), also known as zkCPUs. Contrary to zkEVMs from the last blog, zkVMs is a broader concept applicable to any binary program, making it more versatile in its use cases. zkVMs run precompiled binaries and generate a verifiable proof that the binary ran correctly. This proof can be independently verified by anyone without needing to see the actual inputs, outputs, or intermediate values of the computation.

Unlike zkEVMs, zkVMs are also not tied down to a blockchain. zkVMs can be used for general purposes that are not run on Ethereum. Since zkVMs take precompiled binaries as an input, programs can be written in a variety of programming languages such as Rust, C, and C++, all compiled languages. Considering that Solidity development is often a bottleneck, access to the broader ecosystem of general software is a major benefit.

Inside the zkVM, each assembly instruction in the binary is associated with an R1CS gate to check for verification. For example, in pseudocode, it might look something like this:

if instruction is “add a, b”: add constraint for c = add a,b else if instruction is “mul a, b”: add constraint for c = mul a,b

However, zkVMs have certain limitations. Due to the large amount of assembly instructions and R1CS gates for each instruction, it is typically more suitable for smaller binaries with a manageable complexity. Running larger and more complex programs might be impractical or less efficient using a zkVM. For larger binaries, circuits need to be optimized in order to allow for fast ZKP generation. Specific computations need a more optimized method to prevent slowdowns in the zkVM.

RISC Zero is a zkVM developed for general-purpose use. On a simple level, a binary is used as the input and the VM extracts each assembly instruction. For example, here is how memory loads are handled in RISC Zero:

pub fn ram_load(&mut self, triple: &TripleWord) -> Result<bool> {
trace!("RAM_LOAD[{}]: {triple:?}", self.cycle);
self.start();
self.code[ControlIndex::RamLoad] = BabyBearElem::ONE;
self.code[ControlIndex::Info] = BabyBearElem::new(triple.addr);
(
self.code[ControlIndex::Data1Lo],
self.code[ControlIndex::Data1Hi],
) = split_word16(triple.data[0]);
(
self.code[ControlIndex::Data2Lo],
self.code[ControlIndex::Data2Hi],
) = split_word16(triple.data[1]);
(
self.code[ControlIndex::Data3Lo],
self.code[ControlIndex::Data3Hi],
) = split_word16(triple.data[2]);
self.next()
}

After extracting all assembly instructions, they are run one by one as listed below. Along the way, constraints are added to the main circuit, which proves the validity of the program.

pub fn body(&mut self) -> Result<()> {
debug!("BODY");
loop {
self.start();
self.code[ControlIndex::Body] = BabyBearElem::ONE;
if !self.next_fini(FINI_TAILROOM)? {
break;
}
}
Ok(())
}

Rather than using a zkSNARK, RISC Zero uses a zkSTARK that includes four processing phases. Phase 1 is essentially the commitment phase. The prover creates trace polynomials after each step and commits them to a Merkle tree. Phase 2 allows the validator to validate the polynomials and send PLONK-mixing parameters, which the prover uses to generate Merkle roots. Phase 3 and Phase 4 use the DEEP-FRI technique, which asks the prover for values of a polynomial outside of the original Merkle tree commitments. Of course, this is a vast oversimplification of the entire process.

Conclusion

This two-part article toured the fundamentals of ZKPs — from zkSNARKs and their implications in zkEVMs, ZK programming languages, and zkBridges all the way to ZK payments, ZK hardware acceleration, and zkVMs. The world of zero-knowledge is a vast and fascinating one, and we’ve just touched the surface.

This guide can be used to kickstart further exploration of ZKPs. ZKPs have become increasingly important to ensure the security and validity of transactions on the blockchain as well as prove knowledge in other aspects. As the ZK landscape continues to evolve and expand, the potential for real-world applications will grow exponentially.

Special thanks to fellow Zellic engineer Mohit Sharma for his help and input in this article.

About Us

Zellic specializes in securing emerging technologies. Our security researchers have uncovered vulnerabilities in the most valuable targets, from Fortune 500s to DeFi giants.

Developers, founders, and investors trust our security assessments to ship quickly, confidently, and without critical vulnerabilities. With our background in real-world offensive security research, we find what others miss.

Contact us for an audit that’s better than the rest. Real audits, not rubber stamps.