Building the App Circuit

Our app's goal is to allow anyone to prove to an on-chain smart contract that the age of an Ethereum account is older than block X. We are going to implement this app step by step. You can find the finished version in this Github repo.

Writing the Circuit

Edit circuit.go and write our circuit.

package age

import ""

type AppCircuit struct{}

func (c *AppCircuit) Allocate() (maxReceipts, maxStorage, maxTransactions int) {
	// Our app is only ever going to use one transaction data at a time so
	// we can simply limit the max number of data for transaction to 1 and
	// 0 for all others
	return 0, 0, 1

func (c *AppCircuit) Define(api *sdk.CircuitAPI, in sdk.DataInput) error {
	// Making a datastream is not necessarily useful here since we only
	// want to access the first element. But we will learn about this
	// API later in another section.
	txs := sdk.NewDataStream(api, in.Transactions)

	// We only have one element
	tx := sdk.GetUnderlying(txs, 0)
	// This is our main check logic. This satisfies our spec of "there
	// exists a tx with nonce = 0"
	api.Uint248.AssertIsEqual(tx.Nonce, sdk.ConstUint248(0))

	// Output variables can be later accessed in our app contract
	api.OutputUint(64, tx.BlockNum)

	return nil

Testing the Circuit

Edit circuit_test.go for circuit testing. First, we assign correct values to the circuit input. Then, we use test.ProverSucceeded to test if our circuit can successfully generate a proof using the correct input. Read more on testing here

// ...

func TestCircuit(t *testing.T) {
	app, err := sdk.NewBrevisApp() 

	txHash := common.HexToHash(
	// code to get tx from ETH node omitted...
	// tx := queryTransactionFromEthNode(...)
		Hash:                 txHash,
		ChainId:              tx.ChainId(),
		BlockNum:             receipt.BlockNumber,
		Nonce:                tx.Nonce(),
		GasTipCapOrGasPrice:  tx.GasTipCap(),
		GasFeeCap:     	      tx.GasFeeCap(),
		GasLimit:             tx.Gas(),
		From:                 from,
		To:                   *tx.To(),
		Value:                tx.Value(),

	appCircuit := &AppCircuit{}
	appCircuitAssignment := &AppCircuit{}

	circuitInput, err := app.BuildCircuitInput(appCircuitAssignment)

	test.ProverSucceeded(t, appCircuit, appCircuitAssignment, circuitInput)

Spin Up a Prover for Your AppCircuit

proverService, err := prover.NewService(&AppCircuit{}, config)
// ...
err = proverService.Serve(33247)
// ...

Then, we run the main program to start up the prover

go run main.go
Console Output
circuit compiled in 215.946667ms, number constraints 299986
circuit digest 0xce0d529a429f59067eab8d41362199c5bdebd817cf483edb550ad0c074f2b2f1
trying to read setup from cache...
no setup matching circuit digest 0xce0d529a429f59067eab8d41362199c5bdebd817cf483edb550ad0c074f2b2f1 is found in $HOME/circuitOut
>> setup
size system 300501
size lagrange 524288
fetching SRS from cache
SRS not found in mem cache
fetching SRS from fs cache /Users/patrickmao/kzgsrs/kzgsrs-bls12_377-19
SRS not found in fs cache
Downloading SRS from url
downloading file
writing SRS to fs cache
init SRS disk cache dir /Users/patrickmao/kzgsrs/
setup done in 1.819163208s

// vk hash: 0x1b3738642fbaef19b5b7f1d6e516905af845deb4215458037f76a5d435aee13e

50380984 bytes written to /Users/patrickmao/circuitOut/0xce0d529a429f59067eab8d41362199c5bdebd817cf483edb550ad0c074f2b2f1/pk
49184 bytes written to /Users/patrickmao/circuitOut/0xce0d529a429f59067eab8d41362199c5bdebd817cf483edb550ad0c074f2b2f1/vk

>> serving prover at localhost:33247

The VK Hash

Notice the highlighted log that look like this

// vk hash: 0x1b3738642fbaef19b5b7f1d6e516905af845deb4215458037f76a5d435aee13e

This is the hash of your circuit's verifying key. You must store this hash in your contract and check it when handling contract callbacks. More on this in later steps.

Last updated