This guarantees that a verified result—say, a DeFAI agent’s decision or a DePIN sensor’s classification—actually stems from authentic model execution without revealing the input data or model internals.
Some concrete examples:
- Decentralized identity systems verifying biometric feature extraction without leaking data
- Fraud detection models proving suspicious transaction likelihood without exposing logic
- On-chain oracles attesting to sensory AI predictions
Without verifiable evidence, these models would require implicit trust, risking oracle/data manipulation. zkML breaks this trust assumption by cryptographic means.
Setting Up the Environment for Ezkl
Let me start by outlining prerequisites and environment setup. For this example, I used ezkl version 0.5.1 (check official repo for updates).
Prerequisites:
- Rust (stable, 1.70+)
- Python 3.8+ (for PyTorch to ONNX export)
- Node.js (optional, for running Solidity verification scripts)
- Solidity compiler (Solc 0.8.x recommended)
Install ezkl CLI via Cargo:
cargo install ezkl
Then clone the sample model repo or create your own PyTorch model (next section).
I suggest using a virtualenv and pinning all dependencies for a repeatable environment. Also, run tests locally before deploying on testnet/mainnet due to gas costs.
From PyTorch to ONNX to zk-SNARK Circuit
Ezkl supports ONNX format as the intermediary to generate zk-SNARK circuits. So the pipeline goes:
PyTorch (or other ML framework) → export model → ONNX → ezkl circuit
Here's a basic PyTorch model for XOR classification:
import torch
import torch.nn as nn
class XORModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(2, 4)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(4, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.sigmoid(self.fc2(x))
return x
model = XORModel()
model.eval()
## Export sample input
dummy_input = torch.tensor([[0., 0.]])
torch.onnx.export(model, dummy_input, "xor.onnx", opset_version=12)
Save this code as export_xor.py and run it to get xor.onnx.
This ONNX model can then be fed into ezkl.
Generate the circuit with:
ezkl compile xor.onnx --output xor
This produces circuit files to the xor directory, representing the arithmetic constraints for zkSNARK proof.
I remember first trying this and hitting opset mismatches — opset 12 works well with ezkl right now, but always confirm with the latest docs.
Generating zkSNARK Proofs with Ezkl
Proof generation happens off-chain using the circuit and the supplied inputs. Ezkl comes with Rust APIs and CLI to facilitate this.
Suppose you want to prove that the input [1, 0] produces output near 1 in the XOR model.
Create a JSON input file input.json:
{
"0": [1, 0]
}
Run proof generation:
ezkl prove --circuit xor/circuit.json --input input.json --prover xor/prover
This produces a proof file (proof.json) which contains the zkSNARK proof data.
What’s nice here is that Ezkl supports reproducible proof generation and optimizing performance by reusing keys.
On-Chain zkSNARK Proof Verification
Now to the Solidity part. Ezkl auto-generates a Solidity verifier contract from the circuit:
ezkl verifier --circuit xor/circuit.json --output contracts/XorVerifier.sol
The Solidity contract exposes a verifyProof function typical of zkSNARK verifier contracts:
function verifyProof(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[] memory input
) public view returns (bool) {
// auto-generated verifier logic
}
Deploy this contract on your testnet, then call this function passing proof parameters serialized from proof.json.
Here's a snippet in Hardhat (TypeScript) for invoking the verifier:
const proof = require('./proof.json');
const verifier = await ethers.getContractAt('XorVerifier', verifierAddress);
const verified = await verifier.verifyProof(
proof.proof.a,
proof.proof.b,
proof.proof.c,
proof.publicSignals
);
console.log(`Verified: ${verified}`);
If everything checks out, the verifier will return true, confirming the on-chain verifiable proof for the AI inference.
Comparing zkML, opML, and TEE for AI On-Chain
There’s often confusion around zkML vs OPML (Oracle Proof ML) vs Trusted Execution Environments (TEE) when implementing verifiable AI. Here’s how I differentiate them:
| Feature / Aspect |
zkML |
opML |
TEE |
| Proof Type |
zk-SNARK zero-knowledge proofs |
Oracle-based proofs |
Hardware attestation |
| On-chain Verification |
Yes, succinct and trustless |
Yes, but relies on oracle trust model |
No (usually off-chain verification) |
| Data Privacy |
Strong (input/output obfuscation) |
Medium (depends on oracle) |
Medium (HW security boundary) |
| Performance Overhead |
High off-chain proof generation |
Lower (oracle submits proof) |
Low but hardware-dependent |
| Threat Model |
Cryptographic soundness |
Requires trusted oracle infrastructure |
Vulnerable to side-channels |
In projects aiming for minimal trust assumptions and privacy (like DeFAI agents on EVM chains), zkML via Ezkl shines. But if you need lower latency or lack zk-tooling expertise, opML or hardware TEEs can be options. And of course, hybrid models also exist.
If you want a deeper dive into oracle-centric approaches, see the related ora-protocol-opml-integration page.
Security Considerations and Best Practices
Now, some concrete security insights from my experience integrating zkML into Solidity workflows:
- Session keys and spending limits: The verifier contract should be minimal and read-only to avoid exploitable state changes.
- Proof replay attacks: Use unique oracles or nonce inputs in the circuit to prevent adversaries replaying old proofs.
- Private keys: Proof generation keys must be securely stored off-chain — exposure compromises proof soundness.
- Unlimited approvals: Avoid having the verifier contract manage token approvals or funds unless strictly necessary.
- Testnets first: Always test cryptographic proof cycles on testnet before mainnet due to fees and debug complexity.
- Circuit complexity: Larger networks increase proving time exponentially — keep models small or pruned.
- Compiler versions: The Solidity verifier contracts depend on specific abi-encoding in the zk prover — mismatches cause gas spikes or failures.
These security footguns are easy to overlook until they bite in production. Personally, I run automated audit tools like Slither and Aderyn on the verifier contracts to catch low-hanging vulnerabilities.
Conclusion and Next Steps
This tutorial showcased how to go from standard PyTorch models to verifiable AI inference on-chain using zkML with Ezkl — covering environment setup, conversion to ONNX, zkSNARK proof generation, and Solidity verification.
The tooling is still evolving. Proof generation times and gas usage remain a challenge, especially for complex networks, but the promise of cryptographically sound DeFAI is compelling for trustless applications.
If you want to automate security audits or implement continuous verification pipelines for your zkML-based contracts, check out our smart-contract-ci-cd-pipeline guide.
Got errors with Ezkl CLI flags or verifier deployment? Our automated-solidity-security-audit and solidityscan-ai-vulnerability-detection pages have practical troubleshooting tips.
Finally, curious about the cryptographic details of how Ezkl circuits find constraints and why Rust is the chosen language? The source code and docs offer a treasure trove, but I believe hands-on testing is still the fastest way to mastery.
What’s your take? Have you tried zkML on your own AI agents or DeFAI protocols? Drop your experience or questions — and happy proving!