#
Getting Started
#
Tutorial 👾
You can easily create an .onnx
file using pytorch
. For samples of Onnx files see here. To see how to generate Onnx files using python, check out the notebooks.. You'll also need an input.json
file with sample inputs and outputs of your model.
Sample onnx files are also available in the repo .
#
Initializing the project
To generate a proof on one of the examples, first install ezkl
Put a model file (network.onnx
) and input file (input.json
) into your working directory, e.g. with something like:
cp ~/ezkl/examples/onnx/4l_relu_conv_fc/network.onnx ./
cp ~/ezkl/examples/onnx/4l_relu_conv_fc/input.json ./
To display ezkl
's understanding of the model in the CLI, run:
ezkl table -M network.onnx
You can always check the options available for a command by typing the command with --help
. For example, ezkl table
will show you the options available for the table
command. This will provide you with the most up-to-date information on a given command's usage and the cli spec.
# list all available commands
ezkl --help
#
Proving Backend (Lilith)
Running ZKML proofs can be computationally expensive. We've made the process easier by providing a backend service that can run the proofs for you.
If you're interested in using the Lilith backend, you can register your interest here.
#
Setting circuit parameters
Our circuit is configured with the settings.json
file. This is created with the gen-settings
command.
ezkl gen-settings
This will produce a settings.json
file you can use for your circuit. However, you can fine-tune your circuit to optimize for accuracy or CPU/memory usage with the calibrate-settings
command:
ezkl calibrate-settings --target resources
In this example, we set the --target
to "resources" so that we can optimize for CPU and memory usage. The other option is "accuracy", which optimizes for accuracy given the fixed point representation of the input model. Our circuit parameters are generated, then saved to settings.json
.
Download the appropriate SRS:
ezkl get-srs
From the network.onnx
onnx file, we will create a settings.json
file that uses the py_run_args
file to specify the visibility of the inputs, outputs and paramaters of the model.
Once we have created the settings file, we can calibrate it using the ezkl.calibrate_settings
command to optimize for either accuracy
or resources
using the input to the model (input.json
), and the model itself (network.onnx
). The accurary target will optimize the circuit for accuracy given the fixed point representation of the input model. The resources target will optimize the circuit for CPU and memory usage.
Check out this colab notebook for more context around this code snippet.
py_run_args = ezkl.PyRunArgs()
py_run_args.input_visibility = "public"
py_run_args.output_visibility = "public"
py_run_args.param_visibility = "fixed" # "fixed" for params means that the committed to params are used for all proofs
res = ezkl.gen_settings()
assert res == True
res = await ezkl.calibrate_settings(target="resources")
assert res == True
For performance reaons, you can only generate settings using the hub, python and cli environments. Stay tuned for updates!
#
Compiling the model
From the onnx file, we will create a .ezkl
file that uses the settings to convert the onnx model to a format ready for proving.
ezkl compile-circuit -M network.onnx -S settings.json --compiled-circuit network.ezkl
From the network.onnx
onnx file, we will create a network.compiled
file that uses the settings.json
file to convert the onnx model to a format ready for proving. Check out this colab notebook for more context around this code snippet.
model_path = os.path.join('network.onnx')
compiled_model_path = os.path.join('network.compiled')
settings_path = os.path.join('settings.json')
data_path = os.path.join('input.json')
py_run_args = ezkl.PyRunArgs()
py_run_args.input_visibility = "public"
py_run_args.output_visibility = "public"
py_run_args.param_visibility = "fixed" # "fixed" for params means that the committed to params are used for all proofs
res = ezkl.gen_settings(model_path, settings_path, py_run_args=py_run_args)
assert res == True
res = await ezkl.calibrate_settings(data_path, model_path, settings_path, "resources")
res = ezkl.compile_circuit(model_path, compiled_model_path, settings_path)
assert res == True
For performance reaons, you can only compile ONNX models using Lilith, python and cli environments. Stay tuned for updates!
#
Creating the circuit
Now, we use setup
to create a proving and verifying key for our circuit, using the SRS and our compiled .ezkl
onnx model.
ezkl setup -M network.ezkl --srs-path=kzg.srs --vk-path=vk.key --pk-path=pk.key
This creates the verification key, proving key, and circuit settings in the locations you specify.
Note: You can view the options associated to a subcommand such as
setup
by typingezkl setup
with no parameters. If you provide some but not all required parameters,ezkl
will tell you what else it needs.
From the compiled model and SRS (structured reference string), we will setup the circuit parameters consisting of the proving and verifying keys. Check out this colab notebook for more context around this code snippet.
compiled_model_path = os.path.join('network.compiled')
srs_path = os.path.join('kzg.srs')
pk_path = os.path.join('test.pk')
vk_path = os.path.join('test.vk')
# get public srs from kzg ceremony, saved to srs path.
res = ezkl.get_srs(srs_path, settings_path)
# setup the circuit and make sure the keys are generated afterwards.
res = ezkl.setup(
compiled_model_path,
vk_path,
pk_path,
srs_path,
)
assert res == True
assert os.path.isfile(vk_path)
assert os.path.isfile(pk_path)
The EZKL Engine npm package supports the setup command. Though we do not recommend it. We recommend using Lilith, python or cli environments for performance reasons. Stay tuned for updates!
===
#
Making a proof
First we generate a witness file.
ezkl gen-witness
Next we will generate a proof that the model was correctly run on private inputs (this is the default setting)..
ezkl prove
To generate a proof, we first need to make a witness file. We can do this by running a forward pass using the input data on the compiled model.
We can use this witness, along with the compiled model, proving key and SRS to generate a proof that the model was correctly run on public inputs.
Check out this colab notebook for more context around this code snippet.
# generate witness
res = ezkl.gen_witness()
# generate proof
res = ezkl.prove()
Generate a witness file using the form rendered below.
'use client'
import {
FileInput,
Label,
Button,
Alert,
Spinner as _Spinner,
Modal,
} from 'flowbite-react'
import React, { useEffect, useState } from 'react'
import { formDataSchema } from './parsers'
import { stringify } from 'json-bigint'
import { useSharedResources } from '../EngineContext'
export default function GenWitness() {
const { engine, utils } = useSharedResources()
const [openModal, setOpenModal] = useState<string | undefined>()
const [alert, setAlert] = useState<string>('')
const [warning, setWarning] = useState<string>('')
const [loading, setLoading] = useState(false)
const [witness, setWitness] = useState({})
const [witnessResult, setWitnessResult] = useState<string>('')
const [buffer, setBuffer] = useState<Uint8Array | null>(null)
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const formInputs = {
compiled_onnx: formData.get('compiled_onnx'),
input: formData.get('input'),
}
// Validate form has valid inputs (zod)
const validatedFormInputs = formDataSchema.safeParse(formInputs)
if (warning) setWarning('')
if (!validatedFormInputs.success) {
setAlert('Please upload all files')
return
}
// Clear alert and warning
if (alert) setAlert('')
// Missing data
if (
validatedFormInputs.data.compiled_onnx === null ||
validatedFormInputs.data.input === null
) {
setAlert('Please upload all files')
return
}
let files = {
compiled_onnx: validatedFormInputs.data.compiled_onnx,
input: validatedFormInputs.data.input,
}
setLoading(true)
/* ================== ENGINE API ====================== */
utils
.handleGenWitnessButton(files as { [key: string]: File })
.then(({ output, executionTime }) => {
setBuffer(output)
// Update result based on the outcome
setWitnessResult(
output
? `Witness generation successful. Execution time: ${executionTime} ms`
: 'Witness generation failed',
)
const witness = engine.deserialize(output)
console.log('witness', witness)
setWitness(witness)
})
.catch((error) => {
console.error('An error occurred:', error)
setWarning(`Witness generation failed: ${error}`)
})
setLoading(false)
}
return (
<div className='flex flex-column justify-around'>
{buffer && !warning ? (
<div className='flex flex-col justify-around'>
<h1 className='text-2xl mb-6 '>{witnessResult}</h1>
<div className='flex flex-col flex-grow w-full items-center justify-around'>
<Button
className='w-full flex-grow'
type='submit'
onClick={() => utils.handleFileDownload('witness.json', buffer)}
>
Download Witness
</Button>
<Button
className='w-full flex-grow mt-4'
onClick={() => setOpenModal('default')}
data-modal-target='witness-modal'
data-modal-toggle='witness-modal'
>
Show Witness
</Button>
<Button className='w-full flex-grow mt-4' onClick={() => setBuffer(null)}>
Reset
</Button>
<Modal
show={openModal === 'default'}
onClose={() => setOpenModal(undefined)}
>
<Modal.Header>Witness File Content: </Modal.Header>
<Modal.Body className='bg-black'>
<div className='mt-4 p-4 bg-black-100 rounded'>
<pre className='blackspace-pre-wrap' style=13px>
{stringify(witness, null, 6)}
</pre>
</div>
</Modal.Body>
</Modal>
</div>
</div>
) : loading ? (
<Spinner />
) : (
<div className='flex flex-col w-full items-center space-y-4'>
<WitnessArtifactForm
handleSubmit={handleSubmit}
alert={alert}
warning={warning}
/>
<Button
type='submit'
color='dark'
className='self-center mt-4 w-full'
onClick={() => populateWithSampleFiles()}
>
Populate with sample files
</Button>
</div>
)}
</div>
)
}
// UI Component
function Spinner() {
return (
<div className='h-full flex items-center'>
<_Spinner size='3xl' className='w-28 lg:w-44' />
</div>
)
}
async function populateWithSampleFiles() {
// Helper to assert that the element is not null
function assertElement<T extends Element>(
element: T | null,
): asserts element is T {
if (element === null) {
throw new Error('Element not found')
}
}
// Names of the sample files in the public directory
const sampleFileNames: { [key: string]: string } = {
compiled_onnx: 'test_network.compiled',
input: 'input.json',
}
// Helper function to fetch and create a file object from a public URL
const fetchAndCreateFile = async (
path: string,
filename: string,
): Promise<File> => {
const response = await fetch(path)
const blob = await response.blob()
return new File([blob], filename, { type: blob.type })
}
// Fetch each sample file and create a File object
const filePromises = Object.entries(sampleFileNames).map(([key, filename]) =>
fetchAndCreateFile(`/data/1l_mlp/${filename}`, filename),
)
// Wait for all files to be fetched and created
const files = await Promise.all(filePromises)
// Select the file input elements and assign the FileList to each
const compiledOnnxInput =
document.querySelector<HTMLInputElement>('#compiled_onnx')
const input = document.querySelector<HTMLInputElement>('#input')
// Assert that the elements are not null
assertElement(compiledOnnxInput)
assertElement(input)
// Create a new DataTransfer to hold the files
let dataTransfers: DataTransfer[] = []
files.forEach((file, idx) => {
const dataTransfer = new DataTransfer()
dataTransfer.items.add(file)
dataTransfers[idx] = dataTransfer
})
compiledOnnxInput.files = dataTransfers[0].files
input.files = dataTransfers[1].files
}
function WitnessArtifactForm({
handleSubmit,
alert,
warning,
}: {
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void
alert: string
warning: string
}) {
return (
<div className='flex flex-col'>
<h1 className='text-2xl mb-6 '>Witnessing</h1>
{alert && (
<Alert color='info' className='mb-6'>
{alert}
</Alert>
)}
{warning && (
<Alert color='warning' className='mb-6'>
{warning}
</Alert>
)}
<form
onSubmit={handleSubmit}
className='flex flex-col flex-grow justify-between'
>
{/* COMPILED ONNX */}
<div>
<Label
color='white'
htmlFor='compiled_onnx'
value='Select Compiled Onnx File'
/>
<FileInput id='compiled_onnx' name='compiled_onnx' className='my-4' />
</div>
{/* INPUT */}
<div>
<Label color='white' htmlFor='input' value='Select Input File' />
<FileInput id='input' name='input' className='my-4' />
</div>
<Button type='submit' color='dark' className='w-full self-center mt-4'>
Generate Witness
</Button>
</form>
</div>
)
}
Use the form rendered below to generate ZKML proofs in the browser right now :)
// Example for pages/Page1.js
'use client'
import {
FileInput,
Label,
Button,
Alert,
Spinner as _Spinner,
Modal,
} from 'flowbite-react'
import React, { useState } from 'react'
import { formDataSchemaProve } from './parsers'
import { stringify } from 'json-bigint'
import { useSharedResources } from '../EngineContext'
export default function Prove() {
const { engine, utils } = useSharedResources()
const [openModal, setOpenModal] = useState<string | undefined>()
const props = { openModal, setOpenModal }
const [alertProof, setAlertProof] = useState<string>('')
const [warningProof, setWarningProof] = useState<string>('')
const [loading, setLoading] = useState(false)
const [proofResult, setProofResult] = useState('')
const [proof, setProof] = useState({})
const [buffer, setBuffer] = useState<Uint8Array | null>(null)
const handleSubmitProve = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const formInputs = {
witness: formData.get('witness'),
pk: formData.get('pk'),
compiled_onnx: formData.get('compiled_onnx'),
srs: formData.get('srs'),
}
// Validate form has valid inputs (zod)
const validatedFormInputs = formDataSchemaProve.safeParse(formInputs)
if (warningProof) setWarningProof('')
if (!validatedFormInputs.success) {
setAlertProof('Please upload all files')
return
}
// Clear alert and warning
if (alertProof) setAlertProof('')
// Missing data
if (
validatedFormInputs.data.witness === null ||
validatedFormInputs.data.pk === null ||
validatedFormInputs.data.compiled_onnx === null ||
validatedFormInputs.data.srs === null
) {
setAlertProof('Please upload all files')
return
}
setLoading(true)
// create file object
const files = {
data: validatedFormInputs.data.witness,
pk: validatedFormInputs.data.pk,
model: validatedFormInputs.data.compiled_onnx,
srs: validatedFormInputs.data.srs,
}
/* ================== ENGINE API ====================== */
utils
.handleGenProofButton(files as { [key: string]: File })
.then(({ output, executionTime }) => {
setBuffer(output)
// Update result based on the outcome
setProofResult(
output
? `Proof generation successful. Execution time: ${executionTime} ms`
: 'Proof generation failed',
)
// Deseralize proof buffer
// TODO - uncomment this line once a new engine bundle is relased
// with patch to web based serialize/deserialize methods.
const proof = engine.deserialize(output)
console.log('proof', proof)
setProof(proof)
})
.catch((error) => {
console.error('An error occurred:', error)
setWarningProof(`Proof generation failed: ${error}`)
})
setLoading(false)
}
return (
<div className='flex flex-column justify-around'>
{buffer && !warningProof ? (
<div className='flex flex-col justify-around'>
<h1 className='text-2xl mb-6 '>{proofResult}</h1>
<div className='flex flex-col flex-grow w-full items-center justify-around'>
<Button
className='w-full flex-grow'
type='submit'
onClick={() => utils.handleFileDownload('test.pf', buffer)}
>
Download Proof File
</Button>
<Button
className='w-full flex-grow mt-4'
onClick={() => props.setOpenModal('default')}
data-modal-target='witness-modal'
data-modal-toggle='witness-modal'
>
Show Proof
</Button>
<Button className='w-full flex-grow mt-4' onClick={() => setBuffer(null)}>
Reset
</Button>
<Modal
show={props.openModal === 'default'}
onClose={() => props.setOpenModal(undefined)}
>
<Modal.Header>Proof File Content: </Modal.Header>
<Modal.Body className='bg-black'>
<div className='mt-4 p-4 bg-black-100 rounded'>
<pre className='blackspace-pre-wrap' style=13px>
{stringify(proof, null, 6)}
</pre>
</div>
</Modal.Body>
</Modal>
</div>
</div>
) : loading ? (
<Spinner />
) : (
<div className='flex flex-col w-full items-center space-y-4'>
<div className='flex w-full items-stretch space-x-8'>
<ProvingArtifactForm
handleSubmit={handleSubmitProve}
alert={alertProof}
warning={warningProof}
/>
</div>
<Button
type='submit'
color='dark'
className='self-center mt-4 w-full'
onClick={() => populateWithSampleFiles()}
>
Populate with sample files
</Button>
</div>
)}
</div>
)
}
// UI Component
function Spinner() {
return (
<div className='h-full flex items-center'>
<_Spinner size='3xl' className='w-28 lg:w-44' />
</div>
)
}
async function populateWithSampleFiles() {
// Helper to assert that the element is not null
function assertElement<T extends Element>(
element: T | null,
): asserts element is T {
if (element === null) {
throw new Error('Element not found')
}
}
// Names of the sample files in the public directory
const sampleFileNames: { [key: string]: string } = {
witness: 'test.witness.json',
pk: 'test.provekey',
compiled_onnx: 'test_network.compiled',
srs: 'kzg',
}
// Helper function to fetch and create a file object from a public URL
const fetchAndCreateFile = async (
path: string,
filename: string,
): Promise<File> => {
const response = await fetch(path)
const blob = await response.blob()
return new File([blob], filename, { type: blob.type })
}
// Fetch each sample file and create a File object
const filePromises = Object.entries(sampleFileNames).map(([key, filename]) =>
fetchAndCreateFile(`/data/1l_mlp/${filename}`, filename),
)
// Wait for all files to be fetched and created
const files = await Promise.all(filePromises)
// Select the file input elements and assign the FileList to each
const witness = document.querySelector<HTMLInputElement>('#witness')
const pk = document.querySelector<HTMLInputElement>('#pk')
const compiled_onnx =
document.querySelector<HTMLInputElement>('#compiled_onnx')
const srsProve = document.querySelector<HTMLInputElement>('#srs_prove')
// Assert that the elements are not null
assertElement(witness)
assertElement(pk)
assertElement(compiled_onnx)
assertElement(srsProve)
// Create a new DataTransfer to hold the files
let dataTransfers: DataTransfer[] = []
files.forEach((file, idx) => {
const dataTransfer = new DataTransfer()
dataTransfer.items.add(file)
dataTransfers[idx] = dataTransfer
})
witness.files = dataTransfers[0].files
pk.files = dataTransfers[1].files
compiled_onnx.files = dataTransfers[2].files
srsProve.files = dataTransfers[3].files
}
function ProvingArtifactForm({
handleSubmit,
alert,
warning,
}: {
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void
alert: string
warning: string
}) {
return (
<div className='flex flex-col'>
<h1 className='text-2xl mb-6 '>Proving</h1>
{alert && (
<Alert color='info' className='mb-6'>
{alert}
</Alert>
)}
{warning && (
<Alert color='warning' className='mb-6'>
{warning}
</Alert>
)}
<form
onSubmit={handleSubmit}
className='flex flex-col flex-grow justify-between'
>
{/* WITNESS */}
<div>
<Label color='white' htmlFor='witness' value='Select Witness File' />
<FileInput id='witness' name='witness' className='my-4' />
</div>
{/* PK */}
<div>
<Label color='white' htmlFor='pk' value='Select Proving Key File' />
<FileInput id='pk' name='pk' className='my-4' />
</div>
{/* COMPILED ONNX MODEL */}
<div>
<Label
color='white'
htmlFor='compiled_onnx'
value='Select Compiled Onnx File'
/>
<FileInput id='compiled_onnx' name='compiled_onnx' className='my-4' />
</div>
{/* SRS */}
<div>
<Label color='white' htmlFor='srs' value='Select SRS File' />
<FileInput id='srs_prove' name='srs' className='my-4' />
</div>
<Button type='submit' color='dark' className='w-full self-center mt-4'>
Generate Proof
</Button>
</form>
</div>
)
}
#
Verification
We can then verify our generated proof with the verify
command:
ezkl verify
Using the proof, settings, verification key and SRS, we can verify our proof.
Check out this colab notebook for more context around this code snippet.
res = ezkl.verify()
assert res == True
Use the form rendered below to verify ZKML proofs in the browser right now :)
'use client'
import {
FileInput,
Label,
Button,
Alert,
Spinner as _Spinner,
} from 'flowbite-react'
import React, { useState } from 'react'
import { formDataSchemaVerify } from './parsers'
import { useSharedResources } from '../EngineContext'
export default function Verify() {
const { utils } = useSharedResources()
const [alertVerify, setAlertVerify] = useState<string>('')
const [warningVerify, setWarningVerify] = useState<string>('')
const [loading, setLoading] = useState(false)
const [verifyResult, setVerifyResult] = useState<string>('')
const handleSubmitVerify = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const formInputs = {
proof: formData.get('proof'),
vk: formData.get('vk'),
settings: formData.get('settings'),
srs: formData.get('srs'),
}
// Validate form has valid inputs (zod)
const validatedFormInputs = formDataSchemaVerify.safeParse(formInputs)
if (warningVerify) setWarningVerify('')
if (!validatedFormInputs.success) {
setAlertVerify('Please upload all files')
return
}
// Clear alert and warning
if (alertVerify) setAlertVerify('')
// Missing data
if (
validatedFormInputs.data.proof === null ||
validatedFormInputs.data.vk === null ||
validatedFormInputs.data.settings === null ||
validatedFormInputs.data.srs === null
) {
setAlertVerify('Please upload all files')
return
}
setLoading(true)
// create file object
const files = {
proof: validatedFormInputs.data.proof,
vk: validatedFormInputs.data.vk,
settings: validatedFormInputs.data.settings,
srs: validatedFormInputs.data.srs,
}
/* ================== ENGINE API ====================== */
utils
.handleVerifyButton(files as { [key: string]: File })
.then(({ output, executionTime }) => {
// Update result based on the outcome
setVerifyResult(
output
? 'Verification successful. Execution time: ' +
executionTime +
' ms'
: 'Verification failed',
)
})
.catch((error) => {
console.error('An error occurred:', error)
setWarningVerify(`Verification process failed with an error: ${error}`)
})
setLoading(false)
}
return (
<div className='flex flex-column justify-around'>
{verifyResult && !warningVerify ? (
<div className='flex flex-col justify-around'>
<h1 className='text-2xl mb-4 '>{verifyResult}</h1>
<div className='flex flex-col flex-grow w-full items-center justify-around'>
<Button className='w-full flex-grow' onClick={() => setVerifyResult('')}>
Reset
</Button>
</div>
</div>
) : loading ? (
<Spinner />
) : (
<div className='flex flex-col w-full items-center space-y-4'>
<div className='flex w-full items-stretch space-x-8'>
<VerifyingArtifactForm
handleSubmit={handleSubmitVerify}
alert={alertVerify}
warning={warningVerify}
/>
</div>
<Button
type='submit'
color='dark'
className='self-center mt-4 w-full'
onClick={() => populateWithSampleFiles()}
>
Populate with sample files
</Button>
</div>
)}
</div>
)
}
// UI Component
function Spinner() {
return (
<div className='h-full flex items-center'>
<_Spinner size='3xl' className='w-28 lg:w-44' />
</div>
)
}
async function populateWithSampleFiles() {
// Helper to assert that the element is not null
function assertElement<T extends Element>(
element: T | null,
): asserts element is T {
if (element === null) {
throw new Error('Element not found')
}
}
// Names of the sample files in the public directory
const sampleFileNames: { [key: string]: string } = {
srs: 'kzg',
proof: 'test.pf',
settings: 'settings.json',
vk: 'test.key',
}
// Helper function to fetch and create a file object from a public URL
const fetchAndCreateFile = async (
path: string,
filename: string,
): Promise<File> => {
const response = await fetch(path)
const blob = await response.blob()
return new File([blob], filename, { type: blob.type })
}
// Fetch each sample file and create a File object
const filePromises = Object.entries(sampleFileNames).map(([key, filename]) =>
fetchAndCreateFile(`/data/1l_mlp/${filename}`, filename),
)
// Wait for all files to be fetched and created
const files = await Promise.all(filePromises)
// Select the file input elements and assign the FileList to each
const proof = document.querySelector<HTMLInputElement>('#proof')
const settings = document.querySelector<HTMLInputElement>('#settings')
const vk = document.querySelector<HTMLInputElement>('#vk')
const srsVerify = document.querySelector<HTMLInputElement>('#srs_verify')
// Assert that the elements are not null
assertElement(proof)
assertElement(settings)
assertElement(vk)
assertElement(srsVerify)
// Create a new DataTransfer to hold the files
let dataTransfers: DataTransfer[] = []
files.forEach((file, idx) => {
const dataTransfer = new DataTransfer()
dataTransfer.items.add(file)
dataTransfers[idx] = dataTransfer
})
srsVerify.files = dataTransfers[0].files
proof.files = dataTransfers[1].files
settings.files = dataTransfers[2].files
vk.files = dataTransfers[3].files
}
function VerifyingArtifactForm({
handleSubmit,
alert,
warning,
}: {
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void
alert: string
warning: string
}) {
return (
<div className='flex flex-col'>
<h1 className='text-2xl mb-6 '>Verifying</h1>
{alert && (
<Alert color='info' className='mb-6'>
{alert}
</Alert>
)}
{warning && (
<Alert color='warning' className='mb-6'>
{warning}
</Alert>
)}
<form
onSubmit={handleSubmit}
className='flex flex-col flex-grow justify-between'
>
{/* PROOF */}
<div>
<Label color='white' htmlFor='proof' value='Select Proof File' />
<FileInput id='proof' name='proof' className='my-4' />
</div>
{/* SETTINGS */}
<div>
<Label
color='white'
htmlFor='settings'
value='Select Settings File'
/>
<FileInput id='settings' name='settings' className='my-4' />
</div>
{/* VK */}
<div>
<Label color='white' htmlFor='vk' value='Select VK File' />
<FileInput id='vk' name='vk' className='my-4' />
</div>
{/* SRS */}
<div>
<Label color='white' htmlFor='srs' value='Select SRS File' />
<FileInput id='srs_verify' name='srs' className='my-4' />
</div>
<Button type='submit' color='dark' className='w-full self-center mt-4'>
Verify
</Button>
</form>
</div>
)
}
Use the form rendered below to verify ZKML proofs in the browser right now :)
'use client'
import {
FileInput,
Label,
Button,
Alert,
Select,
Spinner as _Spinner,
} from 'flowbite-react'
import React, { useState } from 'react'
import { formDataSchemaEvmVerify } from './parsers'
import { useSharedResources } from '../EngineContext'
enum Hardfork {
Chainstart = 'chainstart',
Homestead = 'homestead',
Dao = 'dao',
TangerineWhistle = 'tangerineWhistle',
SpuriousDragon = 'spuriousDragon',
Byzantium = 'byzantium',
Constantinople = 'constantinople',
Petersburg = 'petersburg',
Istanbul = 'istanbul',
MuirGlacier = 'muirGlacier',
Berlin = 'berlin',
London = 'london',
ArrowGlacier = 'arrowGlacier',
GrayGlacier = 'grayGlacier',
MergeForkIdTransition = 'mergeForkIdTransition',
Paris = 'paris',
Shanghai = 'shanghai',
Cancun = 'cancun',
}
export default function InBrowserEvmVerify() {
const { utils } = useSharedResources()
const [alertVerify, setAlertVerify] = useState<string>('')
const [warningVerify, setWarningVerify] = useState<string>('')
const [loading, setLoading] = useState(false)
const [evmVerifyResult, setEvmVerifyResult] = useState<string>('')
const handleSubmitVerify = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const formInputs = {
proof: formData.get('proof'),
bytecode_verifier: formData.get('bytecode_verifier'),
evm_version: formData.get('evm_version'),
}
// Validate form has valid inputs (zod)
const validatedFormInputs = formDataSchemaEvmVerify.safeParse(formInputs)
if (warningVerify) setWarningVerify('')
if (!validatedFormInputs.success) {
setAlertVerify('Please upload all files')
return
}
// Clear alert and warning
if (alertVerify) setAlertVerify('')
if (warningVerify) setWarningVerify('')
// Missing data
if (
validatedFormInputs.data.proof === null ||
validatedFormInputs.data.bytecode_verifier === null ||
validatedFormInputs.data.evm_version === '' ||
validatedFormInputs.data.evm_version === null
) {
setAlertVerify('Please upload all files')
return
}
setLoading(true)
// create file object
const files = {
proof: validatedFormInputs.data.proof,
bytecodeVerifier: validatedFormInputs.data.bytecode_verifier,
}
/* ================== ENGINE API ====================== */
utils
.handleEvmVerifyButton(
files as { [key: string]: File },
validatedFormInputs.data.evm_version as Hardfork,
)
.then(({ output, executionTime }) => {
// Update result based on the outcome
setEvmVerifyResult(
output
? 'In-browser EVM verification successful. Execution time: ' +
executionTime +
' ms'
: 'In-browser EVM verification failed',
)
})
.catch((error) => {
console.error('An error occurred:', error)
setEvmVerifyResult('An error occurred: ' + error)
})
setLoading(false)
}
return (
<div className='flex flex-column justify-around'>
{evmVerifyResult && !warningVerify ? (
<div className='flex flex-col justify-around'>
<h1 className='text-2xl mb-4 '>{evmVerifyResult}</h1>
<div className='flex flex-col flex-grow w-full items-center justify-around'>
<Button className='w-full flex-grow' onClick={() => setEvmVerifyResult('')}>
Reset
</Button>
</div>
</div>
) : loading ? (
<Spinner />
) : (
<div className='flex flex-col w-full items-center space-y-4'>
<VerifyingArtifactForm
handleSubmit={handleSubmitVerify}
alert={alertVerify}
warning={warningVerify}
/>
<Button
type='submit'
color='dark'
className='self-center mt-4 w-full'
onClick={() => populateWithSampleFiles()}
>
Populate with sample files
</Button>
</div>
)}
</div>
)
}
// UI Component
function Spinner() {
return (
<div className='h-full flex items-center'>
<_Spinner size='3xl' className='w-28 lg:w-44' />
</div>
)
}
async function populateWithSampleFiles() {
// Helper to assert that the element is not null
function assertElement<T extends Element>(
element: T | null,
): asserts element is T {
if (element === null) {
throw new Error('Element not found')
}
}
// Names of the sample files in the public directory
const sampleFileNames: { [key: string]: string } = {
proof: 'evm_verify.pf',
bytecode: 'bytecode.code',
}
// Helper function to fetch and create a file object from a public URL
const fetchAndCreateFile = async (
path: string,
filename: string,
): Promise<File> => {
const response = await fetch(path)
const blob = await response.blob()
return new File([blob], filename, { type: blob.type })
}
// Fetch each sample file and create a File object
const filePromises = Object.entries(sampleFileNames).map(([key, filename]) =>
fetchAndCreateFile(`/data/1l_mlp/${filename}`, filename),
)
// Wait for all files to be fetched and created
const files = await Promise.all(filePromises)
// Select the file input elements and assign the FileList to each
const proof = document.querySelector<HTMLInputElement>('#proof')
const bytecode =
document.querySelector<HTMLInputElement>('#bytecode_verifier')
// Assert that the elements are not null
assertElement(proof)
assertElement(bytecode)
// Create a new DataTransfer to hold the files
let dataTransfers: DataTransfer[] = []
files.forEach((file, idx) => {
const dataTransfer = new DataTransfer()
dataTransfer.items.add(file)
dataTransfers[idx] = dataTransfer
})
proof.files = dataTransfers[0].files
bytecode.files = dataTransfers[1].files
}
function VerifyingArtifactForm({
handleSubmit,
alert,
warning,
}: {
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void
alert: string
warning: string
}) {
const [selectedVersion, setSelectedVersion] = useState<Hardfork>(
Hardfork.Istanbul,
)
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const selectedValue = e.target.value as Hardfork
setSelectedVersion(selectedValue)
}
return (
<div className='flex flex-col'>
<h1 className='text-2xl mb-6 '>In-Browser Evm Verifying</h1>
{alert && (
<Alert color='info' className='mb-6'>
{alert}
</Alert>
)}
{warning && (
<Alert color='warning' className='mb-6'>
{warning}
</Alert>
)}
<form
onSubmit={handleSubmit}
className='flex flex-col flex-grow justify-between'
>
{/* PROOF */}
<div>
<Label color='white' htmlFor='proof' value='Select Proof File' />
<FileInput id='proof' name='proof' className='my-4' />
</div>
{/* VERIFIER BYTECODE */}
<div>
<Label
color='white'
htmlFor='bytecode_verifier'
value='Select Evm Verifier Bytecode'
/>
<FileInput
id='bytecode_verifier'
name='bytecode_verifier'
className='my-4'
/>
</div>
{/* EVM VERSION */}
<div>
<Label
color='white'
htmlFor='evm_version'
value='Select Evm Version'
/>
<Select
id='evm_version'
name='evm_version'
className='my-4'
onChange={handleChange}
value={selectedVersion}
>
{Object.keys(Hardfork).map((key) => (
<option key={key} value={Hardfork[key as keyof typeof Hardfork]}>
{key}
</option>
))}
</Select>
</div>
<Button type='submit' color='dark' className='w-full self-center mt-4'>
Verify
</Button>
</form>
</div>
)
}