Brevis App

sdk.BrevisApp is the framework around your custom circuit. It handles the conversion of your data to circuit inputs and interacting with Brevis's system. To create a BrevisApp, use:

import "github.com/brevis-network/brevis-sdk/sdk"
app := sdk.NewBrevisApp(
    1, // data source chain id
    "RPC_URL", // corresponding chain RPC URL, you can find many here: https://chainlist.org/chain/1
    "OUTPUT_DIR", // brevis sdk will save source data into OUTPUT_DIR/input/data.json for future reference
)

Adding Source Data

Source Data Types

The Brevis application circuit supports proving receipt, storage value, and transaction by adding TransactionData, ReceiptData, and StorageData. Developers only need to set up the *required values and the Brevis app will prepare the rest automatically.

ReceiptData

Name
Type
Description

TxHash

Receipt's transaction hash (*required)

BlockNum

Receipt's block number

BlockBaseFee

MptKeyPath

Fields

Array of field information will be used in receipt (Usp to 4 fields in each receipt)

LogFieldData

Name
Type
Description

LogPos

uint

the log's position in the receipt (*required)

IsTopic

bool

Whether the field is a topic (*required)

FieldIndex

uint

The index of the field in either a log's topics or data. (*required)

Contract

The contract from which the event is emitted

EventID

The event ID of the event to which the field belong (aka topics[0])

Value

The value of the field in event, aka the actual thing we care about, only 32-byte fixed length values are supported.

StorageData

Name
Type
Description

BlockNum

Block number used for storage value (*required)

BlockBaseFee

Address

Address used for storage value (*required)

Slot

Storage slot (*required)

Value

Storage value

TransactionData

Name
Type
Description

Hash

Transaction hash (*required)

BlockNum

Receipt's block number

BlockBaseFee

MptKeyPath

LeafHash

Hash of transaction raw data

with rlp prefix.

As of now, brevis will only prove the existence of a transaction, stay tuned for more tx information is usable

Adding Source Data

The data you add here will be available to process in your app circuit.

app.AddReceipt(sdk.ReceiptData{...})
app.AddStorage(sdk.StorageData{...})
app.AddTransaction(sdk.TransactionData{...})

The maximum amount of Receipt/Storage/Transaction data you can add to each type is restricted by the maximum amount you define in your circuit's Allocate function. read more

Each of the three types of data has an index within its type. For example, if you call AddStorage twice:

app.AddStorage(sdk.StorageData{/* StorageA */})
app.AddStorage(sdk.StorageData{/* StorageB */})

Then StorageA will be at index 0, and StorageB will be at index 1.

Pin an Index

You can also pin a piece of data to a specific index. For example, this will pin TransactionA at index 2.

app.AddTransaction(sdk.TransactionData{/* TransactionA */}, 2)

Let's see pinning in a more complete example. Let's say  you defined your Allocate function to allocate 32 data for Receipt, 32 for Storage, and 64 for Transaction.

func (c *AppCircuit) Allocate() (maxReceipts, maxStorage, maxTransaction) {
    return 32, 32, 32
}

Then, you added data queries to your BrevisApp instance:

app.AddReceipt(sdk.ReceiptData{/* ReceiptA */})

app.AddStorage(sdk.StorageData{/* StorageA */})
app.AddStorage(sdk.StorageData{/* StorageB */})

app.AddTransaction(sdk.TransactionData{/* TransactionA */})
// this one is fixed at index 2
const MyFixedSpot = 2
app.AddTransaction(sdk.TransactionData{/* TransactionB */}, MyFixedSpot)

The mental model of this would be:

Notice how there is an empty slot in transactions because we allocated 64 slots for transactions, but only added two. We also fixated TransactionB at index 2, so the slot index 1 remains empty. TransactionB will always be at index 2.

Accessing Data by Index in Circuit

Accessing data by index is closely related to how you allocate data slots. read more about Allocate

func (c *AppCircuit) Define(api *sdk.CircuitAPI, input sdk.CircuitInput) {
    transactions := sdk.NewDataStream(input.Transaction)
    // access transactionB directly
    transactionB := transactions.Get(MyFixedSpot)
}

Building the CircuitInput

sdk.CircutiInput is the packaged data obtained from executing your data queries and converting them into circuit types. This is used in testing, compiling, and proving.

After you have added queries to your BrevisApp, call app.BuildCircuitInput with your circuit definition to build.

// if your circuit has custom inputs, you'll need to supply a correct assignment 
// of those custom inputs
appCircuit := &AppCircuit{MyCustomInput: someCorrectValue}
circuitInput, err := app.BuildCircuitInput(appCircuit)

Submitting the Proof to Brevis

Proof generation relies on a separate set of functions, but once you have a proof, your BrevisApp instance can handle submitting it to Brevis.

To submit your proof to Brevis, you need to first query Brevis RPC for the fee amount and acquire a requestId.

PrepareRequest Input

Name
Type
Description

vk

VerifyingKey

application circuit verifying key

witness

witness.Witness

application circuit witness

srcChainId

uint64

the id of data source chain

dstChainId

uint64

the chain where the proven data is used

appContract

common.Address

developer's contract callback address

callbackGasLimit

uint64

Gas limit for contract callback

queryOption

ZK_MODE: pure zk flow. recommended for developers. OP_MODE: supported by BVN. wip

brevisPartnerKey

string

not required. Developer can use empty string to skip this flow.

calldata, requestId, nonce, feeValue, err := app.PrepareRequest(
    vk, 
    witness,
    srcChainId, 
    dstChainId, 
    refundee, 
    appContract,
    callbackGasLimit,
    gwproto.QueryOption_ZK_MODE.Enum(),
    "")

PrepareRequest Output

Name
Type
Description

calldata

[]byte

transaction calldata

requestId

query key for future reference

nonce

uint64

transaction parameter

feeValue

big.int

proving fee

Submitting the Proof

err := app.SubmitProof(proof)

You can optionally supply success and error callbacks. Note that the option sdk.WithFinalProofSubmittedCallback makes SubmitProof non-blocking. If you want a blocking way to wait for final proof submission, use app.WaitFinalProofSubmitted.

// Choose one:

err := app.SubmitProof(proof, sdk.WithFinalProofSubmittedCallback(...)) // async
// Or
err := app.SubmitProof(proof)
tx, err := app.WaitFinalProofSubmitted(context.Background()) // blocks the routine

Paying the Fee

The provers in the Brevis network only start working after you pay the fee. To pay, call the sendRequest function on the BrevisRequest contract (address) with the feeValue you got from PrepareRequest.

You can pay the fee any time after you acquire the requestId and feeValue from PrepareRequest. This process done in parallel with SubmitProof and WaitFinalProofSubmitted.

Tip: Reducing End-to-end Proof Generation Time

Once PrepareRequest is called AND Brevis receives the fee, Brevis starts proving the proofs that are independent from your proof. If your circuit is big and wants to minimize the proof generation time, you can call PrepareRequest first, then pay the fee. This allows your proof and Brevis's proofs to be generated in parallel.

Last updated