Developing Valence Coprocessor Apps
This guide is designed for developers looking to build Zero-Knowledge (ZK) applications, or "guest programs," for the Valence ZK Coprocessor. It focuses on using the valence-coprocessor-app
template as a foundation. Before diving in, it is beneficial to have a grasp of the concepts presented in Introduction to Valence ZK and Valence ZK System Overview.
The valence-coprocessor-app template repository serves as the primary starting point and practical reference for this guide.
Core Structure of a Coprocessor App
A Valence Coprocessor App (a Guest Program), when based on the template, is primarily structured around two main Rust crates, which compile into the two logical parts of the Guest Program: the Controller and the ZK Circuit.
-
The
controller
Crate (compiles to the Controller): This component contains off-chain logic executed as Wasm within the Valence ZK Coprocessor's sandboxed environment. This Controller acts as an intermediary between user inputs and the ZK circuit. Key responsibilities include receiving input arguments (often JSON) for proof requests, processing inputs to generate a "witness" (private and public data the ZK circuit needs), and interacting with the Coprocessor service to initiate proof generation. The Controller handles proof computation results; it has an entrypoint function the Coprocessor calls upon successful proof generation, allowing the Controller to store the proof or log information. The Controller can utilize a virtual filesystem provided by the Coprocessor, which is FAT-16 based (implying constraints like 3-character file extensions and case-insensitive paths), for persistent data storage. -
The
circuit
Crate (defines the ZK Circuit): This crate defines the ZK Circuit itself. The ZK Circuit is the heart of the ZK application, containing the actual computations and assertions whose correctness will be proven. It's typically written using a specialized language or Domain-Specific Language (DSL) that compiles down to a ZK proving system supported by the Coprocessor (for example, SP1). The ZK Circuit receives the witness data prepared by the Controller. It then performs its defined computations and assertions. If all these pass, it produces a public output (as aVec<u8>
), which represents the public statement that will be cryptographically verified on-chain. This output forms a crucial part of the "public inputs" of the ZK proof.
While these two crates form the core, the template might also include an optional ./crates/domain
crate. This is generally intended for more advanced scenarios, such as defining how to derive state proofs from JSON arguments or for validating block data that might be incorporated within the Coprocessor's operations, though its direct use can vary significantly depending on the specific application's needs.
General Development Workflow
Developing a Coprocessor App typically follows a sequence of steps from setup to deployment and testing:
-
Environment Setup: The initial step involves preparing your development environment. This requires installing Docker, a recent Rust toolchain, and the Cargo Valence subcommand. You would then clone the
valence-coprocessor-app
template repository to serve as the foundation for your new ZK application. For development, you can either use the public Valence ZK Coprocessor service atprover.timewave.computer:37281
or optionally run a local Valence coprocessor instance. -
ZK Circuit Development (
./crates/circuit
): The next phase is to define the logic of your ZK circuit. This involves specifying the exact computations to be performed, the private inputs (the witness) that the circuit will consume, and the public inputs or outputs it will expose. The public output of your ZK circuit (aVec<u8>
) is of particular importance, as this is the data that will ultimately be verified on-chain. It's essential to remember that the first 32 bytes of the full public inputs (as seen by the on-chain verifier) are reserved by the Coprocessor for its own internal root hash; your application-specific public output data will follow these initial 32 bytes. -
Controller Development (
./crates/controller
): Concurrently, you'll develop the Controller logic within thecontroller
crate. This includes implementing the logic to parse incoming JSON arguments that are provided when a proof is requested for your application. You will also need to write the code that transforms these user-provided arguments into the precise witness format required by your ZK circuit. A key part of the Controller is its entrypoint function; this function is called by the Coprocessor service when a proof for your program has been successfully generated and is ready. This entrypoint typically receives the proof itself, the initial arguments that triggered the request, and any logs generated during the process. You must also implement how your Controller should handle this generated proof – a common pattern is to store it to a specific path (e.g.,/var/share/proof.bin
) within its virtual filesystem using astore
command payload directed to the Coprocessor. -
Application Build and Deployment: Once the ZK Circuit (from
circuit
crate) and Controller (fromcontroller
crate) are developed, you build and deploy your Guest Program to the Coprocessor using thecargo-valence
CLI tool. This is typically done by running the commandcargo-valence --socket prover.timewave.computer:37281 deploy circuit --controller ./crates/controller --circuit <circuit-crate-project-name>
from your app template directory. If you're using a local coprocessor instance, you can omit the--socket
parameter. This command compiles both crates (Controller to Wasm) and packages them into a single application bundle, which is then submitted to the Coprocessor service. Upon successful deployment, the Coprocessor service will return a unique Controller ID (e.g.,8965493acca61dfc26193978c4b9a785d24192a0a314143f1c497402859df783
). This Controller ID is crucial as it's used to reference your deployed ZK application in subsequent interactions. -
Requesting Proof Generation: With your Guest Program deployed and its Controller ID known, you can instruct the Coprocessor to generate a proof. This is done using a command like
cargo-valence --socket prover.timewave.computer:37281 prove -j '{"value": 42}' -p /var/share/proof.bin <CONTROLLER_ID>
. You must replace'{"value": 42}'
with the specific JSON input that your Controller is designed to expect. The-p /var/share/proof.bin
argument is a suggestion to your Controller's entrypoint, indicating where it might store the resulting proof within its virtual filesystem after it has been generated and delivered back by the Coprocessor. The argument-j '{"value": 42}'
will be forwarded to./crates/controller/src/lib.rs:get_witnesses
, and the output of this function will be forwarded to the circuit for proving. -
Retrieving Proofs and Public Inputs: After the proof generation is complete and your Controller's entrypoint has handled and stored the proof, you can retrieve it from the program's virtual filesystem. This is typically done with a command like
cargo-valence --socket prover.timewave.computer:37281 storage -p /var/share/proof.bin <CONTROLLER_ID> | jq -r '.data' | base64 -d | jq
. To inspect the public inputs associated with the generated proof (which will include your ZK circuit's specific output along with the Coprocessor's root hash), you can use a command likecargo-valence --socket prover.timewave.computer:37281 proof-inputs -p /var/share/proof.bin <CONTROLLER_ID> | jq -r '.inputs' | base64 -d | hexdump -C
. When examining the hexdump, your circuit's specific output data will appear after the initial 32-byte Coprocessor root hash.
This workflow allows for an iterative development process, enabling you to test and refine your ZK guest programs effectively.
Incorporating Verifiable External State
Guest programs on the Valence Coprocessor can be designed to utilize verifiable state from external blockchains, like Ethereum. This allows ZK applications to react to or incorporate off-chain data in a trust-minimized way. Services such as the state proof service facilitate this by generating state proofs (e.g., Merkle proofs for account balances or storage slots on Ethereum at specific block heights). Currently, this interaction for fetching external state is often achieved via ABI-encoded HTTP calls, though future implementations might support other protocols like WebSockets.
When developing a guest program, you would design its Controller (within the controller
crate) to accept such state proofs as part of its input. The ZK circuit
can then use the proven external state in its computations. The resulting ZK proof from the Valence Coprocessor will thus attest to the correctness of operations performed on this externally verified data. More detailed architectural considerations for this pattern, including how the Coprocessor environment might support or interact with such external proofs, are discussed in ZK Coprocessor Internals.