Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. 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.
  2. 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 the processorMessage 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:

FieldTypeDescription
registryuint64Unique identifier for the deployed ZK guest program. Maps to ZK authorization registry in the Authorization contract.
blockNumberuint64Current or recent block number for replay protection. Prevents reuse of old proofs if validateBlockNumberExecution is enabled for the registry.
authorizationContractaddressAddress of the target Authorization contract. Can be address(0) to allow any authorization contract, or specific address for binding.
processorMessagebytesCore 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:

  1. 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 given registry ID. It also typically checks the blockNumber from the ZKMessage against its record of the last executed block for that registry to prevent replay attacks.

  2. 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 a verify function on the router, passing along the ZK proof, the verifying key (VK), the public inputs for the proof and a payload.

  3. 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.

  4. Dispatch to Processor: If the VerificationRouter confirms the proof's validity, the Authorization contract considers the processorMessage within the ZKMessage to be authentic and authorized for execution. It then typically updates its state for replay protection (e.g., storing the blockNumber as the last executed for that registry) and dispatches the processorMessage to the appropriate Valence Processor contract.

  5. 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.