Integrating ZK Proofs with On-Chain Contracts
This document details the process of integrating Zero-Knowledge (ZK) proofs, generated by a Valence Coprocessor guest program, with the Valence Protocol's on-chain smart contracts. It assumes an understanding of the ZK system as outlined in Valence ZK System Overview and how guest programs are developed as described in Developing Valence Coprocessor Apps.
The core of on-chain integration revolves around submitting the ZK proof and its associated public data to the Authorization
contract (CosmWasm or EVM), which then collaborates with a VerificationRouter
to cryptographically verify the proof's authenticity and correctness. For SP1 proofs, the system uses an SP1VerificationSwitch
that performs dual verification: validating both the program proof (using the provided VK) and the domain proof (using the Coprocessor root commitments).
Preparing Data for On-Chain Submission
After your guest program successfully executes on the ZK Coprocessor and a proof is generated, two key pieces of data are essential for on-chain interaction:
- The ZK Proof: This is the raw cryptographic proof data (e.g., SP1 proof bytes) generated by the Coprocessor, attesting to the correct execution of your guest program's ZK circuit.
- The Circuit's Public Output: Your ZK circuit is designed to produce a public output (
Vec<u8>
). This output is critical because it represents the data that, once proven correct by the ZK proof, will be used to form theprocessorMessage
for execution by the on-chain Valence Processor contract. When the full "public inputs" are presented to the on-chain verifier, the first 32 bytes contain the Coprocessor Root (historical commitments). The remaining bytes are the circuit output your app defines. For how domain and historical openings bind values to this root, see Domain Proofs.
An off-chain system, such as a script, bot, or backend service, is responsible for retrieving these pieces of data from the Coprocessor (typically after the guest program stores them in its virtual filesystem) and then initiating the on-chain transaction.
The ZKMessage
Structure
To submit a ZK-proven action to the Valence Protocol, the off-chain system must construct a ZKMessage
. This structure is specifically designed for the executeZKMessage
function within the Authorization.sol
contract. The ZKMessage
encapsulates all necessary information for the on-chain contracts to process the request:
Field | Type | Description |
---|---|---|
registry | uint64 | Unique identifier for the deployed ZK guest program. Maps to ZK authorization registry in the Authorization contract. |
blockNumber | uint64 | Current or recent block number for replay protection. Prevents reuse of old proofs if validateBlockNumberExecution is enabled for the registry. |
authorizationContract | address | Address of the target Authorization contract. Can be address(0) to allow any authorization contract, or specific address for binding. |
processorMessage | bytes | Core payload dispatched to Processor contract if ZK proof is valid. Contains the actual message to be executed, derived from the circuit's public output. |
On-Chain Verification Sequence
Once the ZKMessage
is constructed and the ZK proof is obtained, the off-chain system submits these to the executeZKMessage
function of the Authorization.sol
contract. The on-chain processing then unfolds as follows:
-
Initial Checks: The Authorization contract first performs several preliminary checks. It verifies if the
msg.sender
(the account submitting the transaction) is authorized to provide proofs for the givenregistry
ID. It also typically checks theblockNumber
from theZKMessage
against its record of the last executed block for thatregistry
to prevent replay attacks. -
Delegation to VerificationRouter: If the initial checks pass, the Authorization contract delegates the task of cryptographic proof verification to the VerificationRouter contract whose address it has been configured with using a
route
. It calls averify
function on the router, passing along the ZK proof, the verifying key (VK), the public inputs for the proof and a payload. -
Proof Verification: The VerificationRouter retrieves the verifier associated by the route and delegates the verification to that verifier. For SP1 proofs, the
SP1VerificationSwitch
performs dual verification:- Program Proof: Uses the provided VK to verify the circuit's computation with your specific public inputs
- Domain Proof: Uses the stored
domainVK
to verify the first 32 bytes (coprocessor root hash) which acts as a commitment to all Coprocessor state integrity
The Coprocessor Root hash implicitly contains all embedded state proofs of domains relevant to the ZK proof, managed via a Sparse Merkle Tree (SMT). Every new block appended to chains relevant to the proof's domain is included in this SMT with a ZK domain proof, and the verifications of these inclusions are cryptographically embedded into this root. Both proofs must pass for successful verification. If valid, the router returns a success status to the Authorization contract.
-
Dispatch to Processor: If the VerificationRouter confirms the proof's validity, the Authorization contract considers the
processorMessage
within theZKMessage
to be authentic and authorized for execution. It then typically updates its state for replay protection (e.g., storing theblockNumber
as the last executed for thatregistry
) and dispatches theprocessorMessage
to the appropriate Valence Processor contract. -
Execution by Processor: The Processor contract receives the
processorMessage
and executes the sequence of on-chain actions (e.g., calls to various Valence Libraries or other smart contracts) as defined within that message. This is where the result of your ZK-proven off-chain computation translates into tangible on-chain state changes.
This integration pathway ensures that off-chain computations, once proven correct by the ZK Coprocessor, can be securely and reliably acted upon by the Valence on-chain contracts.
Verifying Keys
- Guest program VKs can be fetched from the Coprocessor via
GET /api/circuit/vk
(base64) for a given controller context. - For recursive domain proofs, a domain prover service publishes a stable wrapper VK (e.g., via
/api/consts
). On‑chain verifiers can bind to this VK and the expected controller ID.