Proof Composition
Prerequisite Reading: Request a Proof, Proof Lifecycle
Overview
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:
- A proof that block
n
is valid - You want to prove that both blocks
n
andn+1
are valid
Instead of reproving block n
, you can:
- Use the existing proof of block
n
- Prove only block
n+1
under the assumption thatn
is valid - Resolve this assumption by verifying the previous proof within your new proof
This approach is more efficient than reproving everything from scratch.
How it Works
Proof composition works by:
- Requesting a raw Groth16 proof from the Boundless Market
- Using this proof as input to a new zkVM guest program
- Verifying the proof within the guest program
- 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:
let mut requirements = Requirements::new(image_id, Predicate::digest_match(journal.digest()));
if groth16 {
requirements = requirements.with_groth16_proof();
}
We then use this proof as input to the Identity guest program:
// Build the IDENTITY input from the ECHO receipt
let identity_input = (Digest::from(ECHO_ID), echo_receipt);
let identity_guest_env =
Input::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")?;
Finally, we can use the composed proof to interact with a smart contract:
alloy::sol! {
#[sol(rpc)]
interface ICounter {
function increment(bytes calldata seal, bytes32 imageId, bytes32 journalDigest) external;
}
}
// 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?;
Relevant links: Proof Composition Example