Use a Proof
Prerequisite Reading: Request a Proof, Proof Lifecycle
After requesting a proof, the next step is to use that proof in the application's workflow. The exact way proofs are used will vary depending on the architecture of the application. However, there is a common pattern; once a proof is received from the Boundless market, the next step will be to verify that proof on-chain.
It is recommended that the application contract calls the RiscZeroVerifierRouter for verification. This allows handling many types of proofs, and proof system versions seamlessly. In Boundless, the seal, which is often a zk-STARK or SNARK, will usually be Merkle inclusion proof into an aggregated proof. These Merkle inclusion proofs are cheap to verify, and reuse a cached verification result from a batch of proofs verified with a single SNARK.
Proof Verification
The Boundless Foundry Template, walks through a simple application which, with an input number, x:
- Uses a simple guest program to check if x is even.
- Requests, and receives, a proof of x being even from the Boundless Market.
- Calls the
set
function on theEvenNumber
smart contract with the arguments: x and the seal (the proof bytes). - The
set
function verifies the proof that x is even; if the proof is valid, thenumber
variable (in smart contract state) is set to equal x.
Concretely, receiving the proof (see code) from the Boundless Market returns a journal and a seal:
let (journal, seal) = boundless_client
.wait_for_request_fulfillment(request_id, Duration::from_secs(5), expires_at)
.await?;
Using Alloys sol! Macro, the rust types/bindings are generated for the EvenNumber.sol
contract.
To create an EvenNumber
contract instance:
let even_number = IEvenNumber::new(
args.even_number_address,
boundless_client.provider().clone(),
);
To call the set
function on the EvenNumber
contract, a “set number” transaction is created:
let set_number = even_number
.set(U256::from(args.number), seal)
.from(boundless_client.caller());
Finally, this transaction is broadcasted with:
let pending_tx = set_number.send().await.context("failed to broadcast tx")?;
let tx_hash = pending_tx
.with_timeout(Some(TX_TIMEOUT))
.watch()
.await
.context("failed to confirm tx")?;
tracing::info!("Tx {:?} confirmed", tx_hash);
Definition of the set
function on EvenNumber.sol
:
/// @notice Set the even number stored on the contract. Requires a RISC Zero proof that the number is even.
function set(uint256 x, bytes calldata seal) public {
bytes memory journal = abi.encode(x);
verifier.verify(seal, imageId, sha256(journal));
number = x;
}
Calling the set
function will verify the proof via the RISC Zero verifier contract.
The verify
call will revert if the proof is invalid, otherwise the number variable will be updated to x, which is now certainly even.
Each application will have its own requirements and flows, but this is a common pattern and a good starting point for building your own application.
Relevant links: Boundless Foundry Template, Journal, Seal