How to write and audit a secure zkVM application: A practical example with RISC Zero | Smart contract audits from Veridise

How to write and audit a secure zkVM application: A practical example with RISC Zero

Jul 16

| 10 min read

How to write and audit a secure zkVM application: A practical example with RISC Zero

This is the third article in our series exploring the security of zkVMs and the applications built on top of them.

zkVM security series:
📚 Part I: Introduction to Zero-Knowledge Virtual Machines (zkVMs)
📚 Part II: Identifying common vulnerabilities in zkVMs
📚 Part III: Writing and auditing secure zkVM applications: A practical example with RISC Zero

Writing and auditing a zkVM application

A general purpose zkVM greatly eases the pains associated with manual constraint writing, as it removes much of the difference in mindset that is required for manually writing constraints. Rather than requiring developers to think in terms of field arithmetic and constraint systems, zkVMs allow applications to be written in familiar languages and compiled into circuits automatically. However, as we’ve explored in earlier sections, this abstraction doesn’t eliminate all risks. Let’s walk through an example.

RISC Zero is one of the most popular zkVMs, and that is the system we will use for this example. You can see a previous blog post of ours on how we assisted RISC Zero in securing their zkVM implementation. For this example, we will mimic the bug in section “Example: Public key derivation vulnerability in ZK UTXO model” (in Part II blog post) and only focus on the guest portion of the code. Like the above example, we omit the generation of the new note (so this code is only relevant to spending).

For our toy example, we will assume that two dependencies exist:

  • merkletree exposes a verify(root, leaf, path) -> bool function that validates a given leaf exists inside of the provided merkle root and path
  • zkcoin exposes a generate(id, amount, pubkey, privkey) -> (coin, nullifier) function that generates the coin and nullifier for a given id and amount.

Let’s write our simple implementation:

Of the four mentioned vulnerabilities in the above sections, we can be confident that two of them are addressed.

Vulnerability Addressed?
Underconstrained circuit
Weak input validation
Replay & Frontrunning
External dependency relaxed threat model

The underconstrained circuit issue does not apply here, as we are working with the RISC Zero zkVM and assuming the correctness of their components. Additionally, replay attacks are not an issue as the nullifier is publicly committed to the proof, and therefore an attacker “stealing” the proof does no harm to the protocol as long as the nullifier is checked in the smart contract and cannot be double spent. Similarly, frontrunning does not apply in this example (assuming the dependencies are correct), as the update of the smart contract’s state does not rely upon the submitter of the transaction.

One may be led to believe the validity of the coin supply and the enforcement of nullifier relationships to UTXOs are correct. This assumption may be made if the developer expected the zkcoin crate to correctly verify the from_pub is derived from the from_priv. However, if this assertion is not made in the dependency, then the vulnerability in the original section “Example: Public key derivation vulnerability in ZK UTXO model” (in Part II blog post) exists here as well. This bug arises from both “weak input validation” and the “relaxed threat model” of dependencies.

Recommendations for secure zkVM application development

Use dependencies sparingly and validate their behavior

In the Dangers specific to zkVMs” section (in Part II blog post) I detailed how issues can arise by introducing dependencies into a ZK program that were not designed for the adversarial environment zkVMs operate under. Due to these dangers, it is strongly recommended to minimize dependency usage as much as possible. When it is not feasible to reimplement a dependency’s function then there are two points we greatly emphasize:

  1. Validate the inputs to the circuit that are directly/indirectly utilized by the dependency strictly match a defined structure, such as the intended behavioral specification. For example a bytestring input that is parsed as an HTTP request should be validated to conform to the corresponding RFC.
  2. Investigate the behavior and guarantees provided by the dependency. This task is twofold and is time consuming. One must first figure out the intentions of the developers of the library, and what guarantees are documented surrounding the validity of the outputs. After understanding this goal, one should investigate the library itself to confirm it meets both the library developer’s goals and the goals of the consuming ZK application. This includes looking for open issues in the library’s repository and even reviewing the code of the library itself. This is tedious but, again, we have found issues in external libraries that opened critical vulnerabilities in the consuming ZK application.

Map inputs to outputs

A good exercise for preventing input validation bugs and other insufficiently validated/deterministic behavior is to map how the inputs of a program effect its outputs/public inputs. This will require one to make strict documentation surrounding the influence an input has on the execution of a ZK application/circuit and assist in validating the intended behavior is correctly implemented. We have uncovered multiple issues during security reviews during this method and believe it serves as a good model for understanding a program more deeply, regardless if any issues are found.

zkVM security series:
📚 Part I: Introduction to Zero-Knowledge Virtual Machines (zkVMs)
📚 Part II: Identifying common vulnerabilities in zkVMs
📚 Part III: Writing and auditing secure zkVM applications: A practical example with RISC Zero

More by Veridise

Subscribe to our blog

Be the first to get the latest from Veridise — including educational articles on ZK and smart contracts, audit case studies, and updates on our tool development. Delivered twice a month.

smart contract audit cloud

Contact us for a security audit quote

Secure an earlier audit slot by reaching out early.