Q: What is a zero-knowledge proof?
A zero-knowledge proof (or ZKP) is "a method by which one party (the prover) can prove to another party (the verifier) that a given statement is true [without] conveying any additional information".
RISC Zero's zkVM makes it easy to produce ZKPs to prove the correct execution of arbitrary code.
By verifying the receipt, Bob can confirm that the expected code executed and produced the asserted results. Any inputs Alice passes to the program during execution will be private unless Alice chooses to share them.
Building on the zkVM
Q: I'm running into build errors. Where should I look for help?
zkVM Application Design
What do I do with the receipt once I’ve created it?
After Alice creates a receipt, she'll typically pass it to Bob who will want to verify its authenticity.
At a minimum, Bob will need access to the ImageID of the expected program.
For most cases, Bob will want to know what code was run, and will therefore also want the ELF file or the source code that generated it.
Bob can verify the receipt was created by this code by constructing the ImageID from the given ELF file and using it for verification.
In our examples, the receipt is generated and verified within the same program, but typically the receipt will be passed to a third party for verification.
Q: When can information be shared with the guest zkVM? How do you prevent buffer overflows?
A: Data can be sent during program execution from the host to the guest via a memory map. The host-writeable memory is write-once, meaning that adjacent memory regions cannot be overwritten and executed.
How do I know which computations should be performed in the guest zkVM, and which can be offloaded to the host?
A: If you don't need to perform a computation securely, if others don't rely on it, and if it doesn't produce outputs that others rely on, it can probably be performed outside of the zkVM.
However, consider that code run in the RISC Zero zkVM can be shown to behave as expected even on a host that is entirely untrusted. To get the most value out of this guarantee, we recommend dividing the computational labor with an untrusted host in mind. That is, other parties should not need to trust the host's output or operations in order to benefit from the work done in the zkVM.
What exactly is the ImageID of a zkVM application?
A: The ImageID is a unique identifier given to a zkVM application. It cryptographically relates the application binary (ELF) to its produced receipts. This bound is a critical security property that ensures applications run unaltered.
Specifically, the ImageID is a Merklization of the initial zkVM memory state, or MemoryImage, produced when the zkVM loads the application binary. The memory state is hashed to produce a single deterministic value via a pure function resembling:
fn compute_image_id(used_elf_pages, page_size, page_table_addr, pc) -> ImageID
Note: Only the loaded parts of the application binary,
used_elf_pages, are utilized to calculate the ImageID. Consequently, the hashing does not include elements of a compiled binary that do not affect program meaning, e.g., debug information and timestamps.
As a consequence, functionally equivilant binaries, from the zkVM perspective, will result in identical ImageIDs. However, the compiled binaries (ELFs) may be bitwise different if hashed directly from disk. This does not affect the zkVM security model.
Features, Performance, and Limitations
Q: Are performance benchmarks available?
Q: What languages can I use to develop zkVM applications?
A: We recommend Rust for writing zkVM applications. Although technically the zkVM can execute any RISC-V code, we only have documentation and API support for Rust development. Development in C++ is also possible, but proceed at your own risk. You can reference the examples in C++ that were included in the 0.11 release, although we've made substantial changes since that release, and we're available to answer questions on Discord as needed.
Q: What is the maximum execution length for a program running on the zkVM?
A: Since we added support for continuations, the execution length can be very large. So far, we've made proofs for executions that exceed 4 billion cycles, and there's plenty of room to expand that further.
Q: I have a specific Rust crate I'd like to use. Will it work inside the zkVM?
A: Each night, we check the top 1000 Rust crates for zkVM compatibility.
You can see the results here.
As of this writing, 71% of the top 1000 Rust crates work inside the zkVM.
If I want the guest to process large volumes of data during execution, I might be constrained by space limitations. What are my options?
A: If data is loaded from the host to restrict guest program size, the most significant limitation on zkVM data processing is a constraint on instruction cycles. Loading data into the guest costs instruction cycles, as does data processing.
There are workarounds for data limitations if the data is only included to ensure that its integrity becomes part of the proof of computation. If the data can be processed externally and simply needs to be verifiably unchanged, consider processing data externally and sending the guest a Merkle proof or (if no processing is needed) generating a SHA of a large dataset.
In the future, we plan to lift these processing limitations using continuations and recursion.
I’d like to speed up the processing done inside the zkVM. What are my options?
A: For cryptographic operations, it is possible to build ‘accelerator’ circuits such as our implementation of SHA256. Fast cryptography is sufficient to support many ‘DeFi’ applications. For many other applications, it is possible to perform most computation on the host (outside the zkVM) and then verify the results in the zkVM.
The RISC Zero Circuits
Q: Do I need to write a ZK circuit to build on RISC Zero?
Q: What do RISC Zero's circuits do?
RISC Zero has three circuits: one that executes RISC-V code, one that's used for recursion, and one that is used for a STARK-to-SNARK conversion.
- The RISC-V circuit receives an ELF binary file as a public input and private inputs from the host; the output of the RISC-V circuit is a receipt.
- The recursion circuit is specialized to prove the verification of RISC Zero receipts; this circuit is used in order to compress many RISC Zero receipts into a single receipt.
- The STARK-to-SNARK circuit is used to translate a STARK proof into a SNARK proof, which enables on-chain verification.
Q: How did you make your RISC-V circuit?
A: The RISC-V circuit is found in step.cpp.inc and is generated by the make-circuit program. It consists of:
- Code to emulate RISC-V, including decoding RISC-V instructions and constructing the execution trace.
- Code to evaluate the constraint polynomials that check the execution trace.
- Auxiliary data to support structures such as ‘taps’.
Because the data structures supporting all three of these need to match very carefully, we created a ‘circuit compiler’ program that generates code for all three of these systems.
How can we use the ImageID to determine if an application is altered before execution?
A: The ImageID is determined from an application's compiled binary (ELF), explained in detail above.
Someone wishing to confirm that a receipt corresponds to specific Rust source code can locally reproduce a binary targeting the RISC Zero zkVM using our reproducible build tool and verify that the resulting ImageID matches the ImageID in the receipt.
For example, building our builtin zkVM test functions:
cargo risczero build --manifest-path risc0/zkvm/methods/guest/Cargo.toml
will produce similar output to:
ELFs ready at:
ImageID: 417778745b43c82a20db33a55c2b1d6e0805e0fa7eec80c9654e7321121e97af - "target/riscv-guest/riscv32im-risc0-zkvm-elf/docker/risc0_zkvm_methods_guest/multi_test"
ImageID: c7c399c25ecf26b79e987ed060efce1f0836a594ad1059b138b6ed2f123dad38 - "target/riscv-guest/riscv32im-risc0-zkvm-elf/docker/risc0_zkvm_methods_guest/hello_commit"
ImageID: a51a4b747f18b7e5f36a016bdd6f885e8293dbfca2759d6667a6df8edd5f2489 - "target/riscv-guest/riscv32im-risc0-zkvm-elf/docker/risc0_zkvm_methods_guest/slice_io"
These ImageIDs will stay consistent across all builds due to a containerized process working together with Cargo working norms. You can find more about our reproducible builds and how we test them in this pull request.
Q: If the guest zkVM lives on the host machine, can’t the host still tamper with the application?
A: Like other zk-STARKs, RISC Zero’s implementation makes it cryptographically infeasible to generate an invalid receipt:
- If the binary is modified, then the receipt’s seal will not match the ImageID of the expected binary.
- If the execution is modified, then the execution trace will be invalid.
- If the output is modified, then the journal’s hash will not match the hash recorded in the receipt.