App Smart Contract

The final step is to write a smart contract to handle the ZK verified result. There are two integration points where you interact with Brevis's contracts:

  • Calling BrevisRequest.sendRequest to pay the proving fee.

  • Handling circuit outputs in your app contract's callback function called by BrevisRequest.

To streamline this process, you can use the contract SDK

npm install brevis-contracts

Here is an example of a typical app contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@brevis-network/contract-sdk/lib/BrevisApp.sol";
import "@brevis-network/contract-sdk/lib/IBrevisProof.sol";

contract MyAppContract is BrevisApp, Ownable {
    bytes32 public vkHash;

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

    // BrevisQuery contract will call our callback once Brevis backend submits 
    // the proof.
    function handleProofResult(
        bytes32 _requestId,
        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 the verifying key generated
        // from compiling our app circuit. This ensures that the _circuitOutput 
        // is authentic.
        require(vkHash == _vkHash, "invalid vk");

        (address minBlockNum, uint64 sum) = decodeOutput(_circuitOutput);
        
        // other logic that uses the proven data...
    }

    // Suppose in the app circuit you have:
    // api.OutputUint(64, minBlockNum)
    // api.OutputUint(248, sum)
    // Then, we can decode the output the following way
    function decodeOutput(
        bytes calldata output
    ) internal pure returns (uint64, uint248) {
        uint64 minBlockNum = uint64(bytes8(output[0:8])); 
        uint248 sum = uint248(bytes31(output[8:8+31])); 
        return (minBlockNum, sum);
    }

    function setVkHash(bytes32 _vkHash) external onlyOwner {
        vkHash = _vkHash;
    }
}

Paying the Fee

You need to call BrevisRequest.sendRequest with the requestId and the feeValue (as transaction value) you acquire from in the previous step when you call app.PrepareRequest in Go. The parameter _callback is where you specify your app contract address.

BrevisRequest contract
function sendRequest(
    bytes32 _requestId, 
    address _refundee, 
    address _callback
) external payable

Or, you can send a raw transaction using the calldata you acquire from app.PrepareRequest as the transaction call data and the feeValue as the transaction value.

Refunding a Fee after Timeout

requestTimeout a state variable in BrevisRequest. If there is no proof submitted within the requestTimeout, you can call BrevisRequest.refund to refund the fee back to the _refundee you initially specified when calling sendRequest

Handling Circuit Outputs in Contract

Your app contract needs to inherit BrevisApp contract and call its constructor with the address of the BrevisProof contract (Deployment Addresses). It also needs to override the abstract method handleProofResult.

function handleProofResult(
    bytes32 _requestId, 
    bytes32 _vkHash, 
    bytes calldata _appCircuitOutput
) internal virtual {}

Checking the Verifying Key

The vk hash you get from the Compiling & Setup step uniquely identifies of your circuit. You should save the vk hash in your contract. When handling callbacks from the BrevisRequest contract, you must check that the vkHash matches your expected one.

Reading the Circuit Output

The output calls in your circuit definition is 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.

Last updated