Solidity
Proving
On-chain verification is implemented by using a customized verification function. It receives a list of arguments in the same order as returned by the Prover (public output).
Proofstructure must always be returned from theProveras the first returned element (more on that here), which means thatProofstructure must also be passed as the first argument to the verification function.
The verification function should use the onlyVerified() modifier, which takes two arguments: the address of a smart contract and a selector of function that was executed in the Prover contract.
See an example verification function below:
contract Example is Verifier {
function claim(Proof _p, address verifiedArg1, uint verifiedArg2, bytes extraArg) public returns (uint)
onlyVerified(PROVER_ADDRESS, FUNCTION_SELECTOR) {
//...
}
}
proofis not an argument toonlyVerifiedbecause it is automatically extracted frommsg.data.
Data flow
Proving data flow consists of three steps:
Step 1: GuestOutput
It starts at Guest, which returns GuestOutput structure.
GuestOutput consists of just one field - evm_call_result. evm_call_result field is abi encoded Prover function output.
Since Prover returns Proof placeholder as its first returned value, Guest pre-fills length and call_assumptions fields of the Proof structure.
length field of Proof structure is equal to the length of abi encoded public outputs, not including the size of the Proof placeholder.
See the code snippets below for pseudocode:
#![allow(unused)] fn main() { pub struct GuestOutput { pub evm_call_result: Vec<u8>, } }

Step 2: Host output as v_call result
In the next step, the Host replaces the seal field in the Proof placeholder with the actual seal,
which is a cryptographic proof of the Prover's execution.
The Host then returns this via the JSON-RPC v_call method, delivering the seal as a byte string in the result field.
This approach allows the smart contract developer to decode the
v_call result as though they were decoding the Prover function's output directly.
In other words, the v_call result is compatible with, and can be decoded according to, the ABI of the called Prover function.
In this step, the Host also fills in the field callGuestId, which is a hint to the Verifier about the version of the Guest program.
Step 3: Verifier call
Finally, the method on the on-chain smart contract is called to verify the proof. More on that in the next section.
Proof verification
To verify a zero-knowledge proof, vlayer uses a verify function, delivered by Risc-0.
function verify(Seal calldata seal, bytes32 imageId, bytes32 journalDigest) { /* ... */ }
onlyVerified gets seal and journalDigest by slicing it out of msg.data.
length field of Proof structure is used, when guest output bytes are restored in Solidity in order to compute journalDigest.
length field hints the verifier, which bytes should be included in the journal, since they belong to encoding of the public outputs,
and which bytes belong to extra arguments, passed additionally in calldata.
imageId is fixed on blockchain and updated on each new version of vlayer.
ImageId
The ImageId is an indicator of the specific Guest program used to generate a proof.
A simple mental model is that the ImageId is a digest of the ELF file executed within the zkvm and of its boot environment.
More information on ImageId can be found here.
The ImageId can change frequently, especially on testnets, since any update to the Guest code changes the executable bytecode, which in turn changes the ImageId.
This is a desirable feature because it assures developers that the exact Guest code executed is the one they expected.
It also prevents attackers from providing a proof generated by a malicious or incorrect Guest that would falsely attest to a particular state.
The guestCallId returned by the vlayer prover improves error handling and enables the whitelisting of specific ImageIds.
Note: The
callGuestIdfield is not part of thejournalDigestand therefore is not cryptographically validated meaning transaction sender can try to put overwrite this field. However, this does not impact security, since proofs generated for oneImageIdwill fail to verify in the context of a differentImageId.
Data encoding summary
Below, is a schema of how a block of data is encoded in different structures at different stages.

Structures
The Proof structure looks as follows:
struct Proof {
uint32 length;
Seal seal;
CallAssumptions callAssumptions;
}
with Seal having the following structure:
enum ProofMode {
GROTH16,
FAKE
}
struct Seal {
bytes32[8] seal;
ProofMode mode;
}
and the following structure of CallAssumptions:
struct CallAssumptions {
address proverContractAddress;
bytes4 functionSelector;
uint256 settleBlockNumber;
bytes32 settleBlockHash;
}
Note that
Proof,SealandCallAssumptionsstructures are generated based on Solidity code from withsol!macro.
Feature-specific
Libraries
library EmailProofLib {
function verify(UnverifiedEmail memory unverifiedEmail) internal view returns (VerifiedEmail memory);
}
library WebProofLib {
function verify(WebProof memory webProof, string memory dataUrl) internal view returns (Web memory);
function recover(WebProof memory webProof) internal view returns (Web memory);
}
Structures
Unverified Email
The UnverifiedEmail is passed into the EmailProofLib.verify() function. It returns the VerifiedEmail struct, described below.
struct UnverifiedEmail {
string email; // Raw MIME-encoded email
DnsRecord dnsRecord;
VerificationData verificationData;
}
// Describes DNS record, according to DoH spec
struct DnsRecord {
string name;
uint8 recordType;
string data;
uint64 ttl;
}
// Signature data of the DNS record
struct VerificationData {
uint64 validUntil; // Signature expiration timestamp
bytes signature; // DNS Notary signature of the serialized DNS record
bytes pubKey; // Public key used for signature
}
Verified Email
struct VerifiedEmail {
string from; // Sender email address
string to; // Recipient email address
string subject; // Email subject
string body; // Email body
}
Two Proving Modes
To support two proving modes, vlayer provides a set of smart contracts connected to the Verifier contract, one for each mode:
DEVELOPMENT- Automatically deployed with eachProvercontract, but only on development and test networks. This mode will be used if theProofModedecoded fromSEALisFAKE.PRODUCTION- This requires infrastructure deployed ahead of time that performs actual verification. This mode will be used if theProofModedecoded fromSEALisGROTH16.