Proof Types
Prerequisite Reading: Request a Proof, Proof Lifecycle
Default: Merkle Inclusion Proofs
By default, Boundless delivers proofs on-chain as merkle inclusion proofs:
- Proofs are batched together are aggregated into a single Groth16 proof.
- The aggregated proof is verified once on-chain
- 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:
- Cross-chain verification where you need to verify the proof on a different chain
- Integration with other systems that expect a specific proof type
- 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:
-
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
-
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();
....
}