Writing the App Contract

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

yarn add brevis-contracts
AccountAge.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/access/Ownable.sol";
import "./lib/BrevisApp.sol";

contract AccountAge is BrevisApp, Ownable {
    event AccountAgeAttested(address account, uint64 blockNum);

    bytes32 public vkHash;

    constructor(address brevisProof) 
        BrevisApp(IBrevisProof(brevisProof)) 
        Ownable(msg.sender) {}

    // BrevisRequest contract will trigger callback once ZK proof is received.
    function handleProofResult(
        bytes32 _vkHash,
        bytes calldata _circuitOutput
    ) internal override {
        // 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 authentic
        require(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.
        emit AccountAgeAttested(txFrom, blockNum);
    }

    // In app circuit we have:
    // api.OutputAddress(tx.From)
    // api.OutputUint(64, tx.BlockNum)
    function decodeOutput(bytes calldata o) internal pure returns (address, uint64) {
        // txFrom was output as an address
        address 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 logic
    function setVkHash(bytes32 _vkHash) external onlyOwner {
        vkHash = _vkHash;
    }
}

Checking the Verifying Key Hash

Notice that we have a vkHash storage variable in the contract:

bytes32 public 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:

  1. Check if the param _vkHash matches our stored vkHash. This verifies the identity of the circuit which produced the computation result.

  2. 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.

We know in our circuit we did:

api.OutputAddress(tx.From)
api.OutputUint(64, tx.BlockNum)

Therefore, this is how we decode _circuitOutput in the contract:

address txFrom = address(bytes20(o[0:20]));
uint64 blockNum = uint64(bytes8(o[20:28]));

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.

Last updated