Skip to content

Proof Types

Prerequisite Reading: Request a Proof, Proof Lifecycle

Default: Merkle Inclusion Proofs

By default, Boundless delivers proofs on-chain as merkle inclusion proofs:

  1. Proofs are batched together are aggregated into a single Groth16 proof.
  2. The aggregated proof is verified once on-chain
  3. Individual proofs are verified through cheap merkle inclusion proofs into this root

This design is what makes Boundless cost-effective for on-chain verification.

Requesting a specific proof type

While merkle inclusion proofs are efficient for on-chain verification, there may be cases where you need to access the underlying proof instead of a merkle inclusion proof. For example:

  1. Cross-chain verification where you need to verify the proof on a different chain
  2. Integration with other systems that expect a specific proof type
  3. Custom verification logic that requires the full proof

Boundless supports requesting a raw Groth16 proof instead of a merkle inclusion proof. You can specify this in your proof request by setting the proof_type to ProofType::Groth16:











let request = ProofRequest::builder()
    .with_image_url(image_url)
    .with_input(input_url)
    .with_requirements(
        Requirements::new(ECHO_ID, Predicate::digest_match(journal.digest()))
            .with_groth16_proof(),  // Request raw Groth16 proof
    )
    .with_offer(
        Offer::default()
            .with_min_price_per_mcycle(parse_ether("0.001")?, mcycles_count)
            .with_max_price_per_mcycle(parse_ether("0.002")?, mcycles_count)
            .with_timeout(1000)
            .with_lock_timeout(1000),
    )
    .build()?;

Considerations

When choosing between proof types, consider:

  1. Gas Costs
    • Merkle inclusion proofs are much cheaper to verify on-chain
    • Raw Groth16 proofs require full SNARK verification each time. This will increase the price of the proof
  2. Use Case Requirements
    • If you only need on-chain verification, use the default merkle inclusion proof
    • If you need cross-chain verification or raw proof data, use Groth16
    • If you need to compose the proof by verifying it within another zkVM guest program, use Groth16

Example: Proof Composition using Proof Types

In the Proof Composition example, we demonstrate how to compose a proof from multiple proofs.

Composing a proof requires us to verify a previously generated Groth16 proof within the zkVM guest program. This requires us to request a raw Groth16 proof from the Boundless Market.

In the composition example, we first request a raw Groth16 proof from the Boundless Market using the ECHO guest program.











let request = ProofRequest::builder()
    .with_image_url(image_url)
    .with_input(input_url)
    .with_requirements(                                                       
        Requirements::new(ECHO_ID, Predicate::digest_match(journal.digest())) 
            .with_groth16_proof(),  // Request raw Groth16 proof
    )                                                                         
    .with_offer(
        Offer::default()
            .with_min_price_per_mcycle(parse_ether("0.001")?, mcycles_count)
            .with_max_price_per_mcycle(parse_ether("0.002")?, mcycles_count)
            .with_timeout(1000)
            .with_lock_timeout(1000),
    )
    .build()?;

We then provide the Groth16 proof as input to the IDENTITY zkVM guest program, and verify the proof.

use risc0_zkvm::guest::env;
use risc0_zkvm::{Digest, Receipt};
 
fn main() {
    let (image_id, receipt): (Digest, Receipt) = env::read();
    let claim = receipt.claim().unwrap();
    receipt.verify(image_id).unwrap();
    ....
}