Use Case: Program Execution
As this walkthrough describes a more advanced use case for the zkVM, we recommend reviewing both the SDK Quick Start and the simpler Stable Matching walkthrough first.
By definition, a zkVM is designed to prove the correctness of a guest program execution. What makes program execution its own distinct use case, then, is a slightly more complicated scenario where the ‘program to be proven’ is instead taken as the input to the guest program, which implements an interpreter that reads in the ‘program to be proven’ and executes it within the zkVM.
So, in guest program code something like:
At first glance, this pattern may not seem to provide any value over just using program_to_be_proven
as the guest program in the first place. But, we can nonetheless highlight a few applications where adding this level of indirection is a surprisingly powerful tool to enhance the utility of the zkVM.
The first is implicit in the example given above: we can declare the program_to_be_proven
to be a private input, meaning we can prove private programs — enabling applications such as private smart contracts on a blockchain. Notice that the ability to compare the private program to a public digest enables a verifier to confirm desirable properties of the program used in such a deployment, for example that the private program remains consistent across a variety of invocations, or that it is consistent with a claim of correctness issued by a trusted third-party auditor.
A second potential use, orthogonal to the first and somewhat more technical in nature, is that with this guest program the zkVM can — with some thoughtful guesswork as to maximum stack, heap, and output sizes — implement numerous functionalities with a single fixed program and memory model. This can greatly simplify public parameter management for non-transparent provers which may require per-program setup (though the Stwo prover integration is transparent), as well as simplify design and deployment of smart contracts intended to verify zkVM proofs on-chain.
Instead of working through this design at the full complexity of the RISC-V32i instruction set, we will work through a simpler example of the core concept using a much more fundamental computational model: the lambda calculus.
To start, we will follow along with the SDK Quick Start. After installing the zkVM, we run:
to initialize the host and guest programs. We begin by creating a shared library for the guest and host program, which we’ll locate within the guest program crate.
The first part of the library will define terms in the calculus:
This code block defines an Enum
of the possible Term
s available: an indexed Var
, a Lambda
function definition, and an Apply
function application, as well as some useful trait implementations for constructing and debugging. In the rest of the library, we’ll define full expressions in the calculus, which amount to programs that are “executed” via reduction.
The implementation also includes various trait implementations as well as both individual step
and iterative eval
routines that reduce the statement into a stable form.
In order to make our common library work across both the host and guest, we have a few quick steps we need to handle. First, we need to update the guest crate to depend on serde
, which the library uses for serializing and deserializing the Expr
structs to and from the zkVM:
Next, we need to export the common code as a Rust library.
And lastly we need to make this library visible to the host program.
Returning to the guest program, as an Expr
is a program in the lambda calculus, we can now implement our (dead simple) interpreter — for simplicity, we omit any hash-based consistency checking:
Then, we just need a suitable host program that invokes the guest using the private program:
Meanwhile, as desired verification does not require any access to the private program:
In this way, we are able to use the zkVM to prove the correctness of executing a private program.
Was this page helpful?