Your app contract needs to inherit the BrevisApp abstract contract. You can manually copy the required contracts from the quickstart repo, or install Brevis contracts via
yarnaddbrevis-contracts
AccountAge.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.18;import"@openzeppelin/contracts/access/Ownable.sol";import"./lib/BrevisApp.sol";contractAccountAgeisBrevisApp, Ownable {eventAccountAgeAttested(address account, uint64 blockNum);bytes32public vkHash;constructor(address brevisProof) BrevisApp(IBrevisProof(brevisProof)) Ownable(msg.sender) {}// BrevisRequest contract will trigger callback once ZK proof is received.functionhandleProofResult(bytes32_vkHash,bytescalldata_circuitOutput ) internaloverride {// We need to check if the verifying key that Brevis used to verify the // proof generated by our circuit is indeed our designated verifying key. // This proves that the _circuitOutput is authenticrequire(vkHash == _vkHash,"invalid vk"); (address txFrom,uint64 blockNum) =decodeOutput(_circuitOutput);// For this demo, we simply emit an event. But you can do anything with// these proven data.emitAccountAgeAttested(txFrom, blockNum); }// In app circuit we have:// api.OutputAddress(tx.From)// api.OutputUint(64, tx.BlockNum)functiondecodeOutput(bytescalldata o) internalpurereturns (address,uint64) {// txFrom was output as an addressaddress txFrom =address(bytes20(o[0:20]));// blockNum was output as a uint64 (8 bytes) uint64 blockNum =uint64(bytes8(o[20:28]));return (txFrom, blockNum); }// vkHash represents the unique circuit app logicfunctionsetVkHash(bytes32_vkHash) externalonlyOwner { vkHash = _vkHash; }}
Checking the Verifying Key Hash
Notice that we have a vkHash storage variable in the contract:
bytes32public vkHash;
By setting this variable to our previously computed vkhash, when Brevis calls our app contract, we can check the proof, that Brevis has verified before calling our contract, is indeed generated by our circuit.
Handling Proof Result
handleProofResult is where you process the computation result output from your circuit. There are two steps we must do before we use the proven data:
Check if the param _vkHash matches our stored vkHash. This verifies the identity of the circuit which produced the computation result.
Decode the param _circuitOutput.
The output values in your circuit definition are packed in the form of abi.encodePacked(...). The order of the variables is the same as the order you call the output APIs in the circuit.
If you are following this example to build the project, you can deploy this contract. If you are just doing research and would like to see a deployed version, see the deployed contract on Sepolia.