Skip to main content

Overview

For example code of composition in action, please see the Proof Composition Example
Proof composition enables you to build upon existing proofs by verifying them within new zkVM guest programs. This is particularly useful when you want to prove a sequence of related statements without reproving each step. For example, let’s say you have:
  1. A proof that block n is valid
  2. You want to prove that both blocks n and n+1 are valid
Instead of reproving block n, you can:
  1. Use the existing proof of block n
  2. Prove only block n+1 under the assumption that n is valid
  3. Resolve this assumption by verifying the previous proof within your new proof
This approach is more efficient than reproving everything from scratch.

How Composition Works

Proof composition works by:
  1. Requesting a raw Groth16 proof from the Boundless Market
  2. Using this proof as input to a new zkVM guest program
  3. Verifying the proof within the guest program
  4. Building upon the verified result to prove new statements

Example: Composing Echo and Identity Proofs

The Proof Composition example demonstrates how to compose proofs using the Echo and Identity guest programs. First, we request a raw Groth16 proof from the Echo guest program:
# use boundless_market::contracts::{Predicate, Requirements};
# use risc0_zkvm::sha::Digestible;
# let image_id = [0u8; 32];
# let journal = Vec::<u8>::new();
# let groth16 = true;
let mut requirements = Requirements::new(Predicate::digest_match(image_id, journal.digest()));
if groth16 {
    requirements = requirements.with_groth16_proof();
}
We then use this proof as input to the Identity guest program:
# use alloy::{providers::Provider, network::Ethereum};
# use anyhow::Context;
# use boundless_market::{client::Client, contracts::RequestInput, input::GuestEnv};
# use risc0_zkvm::sha::{Digest, Digestible};
# use boundless_market::storage::StorageProvider;
# async fn boundless_proof<P, S>(
#     client: &Client<P, S>,
#     program: impl AsRef<[u8]>,
#     guest_env: GuestEnv,
#     groth16: bool,
# ) -> anyhow::Result<(Vec<u8>, Vec<u8>)>
# where
#     P: Provider<Ethereum> + 'static + Clone,
#     S: StorageProvider,
# { unimplemented!() }
# async fn run<P, S>(
#    boundless_client: Client<P, S>
# ) -> anyhow::Result<()>
# where
#     P: Provider<Ethereum> + 'static + Clone,
#     S: StorageProvider + Clone,
# {
# let ECHO_ID = [0u8; 32];
# let IDENTITY_ELF = b"";
# let echo_receipt = b"";
// Build the IDENTITY input from the ECHO receipt
let identity_input = (Digest::from(ECHO_ID), echo_receipt);
let identity_guest_env =
    RequestInput::builder().write_frame(&postcard::to_allocvec(&identity_input)?).build_env();

// Request a proof from the Boundless market using the IDENTITY guest
let (identity_journal, identity_seal) =
    boundless_proof(&boundless_client, IDENTITY_ELF, identity_guest_env, false)
        .await
        .context("failed to prove IDENTITY")?;
# anyhow::Ok(())
# }
Finally, we can use the composed proof to interact with a smart contract:
# use boundless_market::{client::Client, contracts::ProofRequest};
# use risc0_zkvm::sha::{Digest, Digestible};
# use alloy::{
#    network::Ethereum,
#    primitives::{utils::parse_ether, address, Address, Bytes, B256},
#    providers::Provider,
#    signers::local::PrivateKeySigner,
#    sol_types::SolCall,
# };
# use std::time::Duration;
alloy::sol! {
    #[sol(rpc)]
    interface ICounter {
        function increment(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external;
    }
}

# async fn interact(request: ProofRequest) -> anyhow::Result<()> {
# let boundless_client = Client::builder().build().await?;
# let identity_journal = Vec::<u8>::new();
# let identity_seal = Bytes::new();
# let IDENTITY_ID = [0u8; 32];
# let TX_TIMEOUT = Duration::from_secs(30);
// Interact with the Counter contract using the composed proof
let counter_address = address!("0x000000000000000000000000000000000c0077e5");
let counter = ICounter::ICounterInstance::new(counter_address, boundless_client.provider().clone());
let journal_digest = B256::from_slice(identity_journal.digest().as_bytes());
let image_id = B256::from_slice(Digest::from(IDENTITY_ID).as_bytes());
let call_increment =
    counter.increment(identity_seal, image_id, journal_digest).from(boundless_client.caller());

// Execute the transaction
let pending_tx = call_increment.send().await?;
let tx_hash = pending_tx
    .with_timeout(Some(TX_TIMEOUT))
    .watch()
    .await?;
# Ok(())
# }