⏱️ Lectura: 10 min
An AI auditor found in 2026 what months of automated scans had missed: a critical soundness flaw in the openvm-pairing library of the OpenVM zkVM, cataloged as CVE-2026-46669. The bug allowed a malicious prover to forge any pairing equality, which breaks the de facto security of any system built on Groth16, PLONK with KZG, or BLS signatures that depended on that library.
📑 En este artículo
The finding was produced by zkao, the AI auditor from the zkSecurity firm, after more than nine and a half hours of continuous scanning over the OpenVM codebase. The human team validated the exploitability, built a minimal PoC, coordinated disclosure with the maintainers, and the flaw was fixed in OpenVM 1.6.0.
TL;DR
- zkao, zkSecurity’s AI auditor, found a critical soundness bug in openvm-pairing after 9.5 hours of scanning.
- The flaw is CVE-2026-46669 and was fixed in OpenVM 1.6.0, commit a720e2c.
- It allowed a malicious prover to forge any pairing equality, breaking Groth16, KZG, and BLS.
- The cause: a missing subfield check on the scaling factor in the pairing verification.
- Months earlier, scans with Opus 4.6, Codex 5.3, Opus 4.7, and Codex 5.4 had not found a real exploitable bug.
- The bug does not affect the zkVM’s proving system itself, only projects that use openvm-pairing.
- zkSecurity confirmed the exploitability, built a minimal PoC, and coordinated disclosure with OpenVM.
- All known OpenVM partners had already updated to version 1.6.0, according to zkSecurity.
Introduction
This finding is the second in a series zkSecurity has been publishing on AI-assisted code audits. The first case was Cloudflare’s CIRCL cryptographic library; in this entry the target was something far more complex: a complete zkVM, that is, a virtual machine that generates zero-knowledge proofs of the execution of an arbitrary program.
OpenVM is a framework for building modular zkVMs in Rust, designed so that different projects can assemble their own virtual machine with the cryptographic components they need. One of those components is openvm-pairing, the guest library responsible for pairing operations over elliptic curves. That is exactly where the bug appeared, not in the core of the proving system.
What happened
Four months before this finding, zkSecurity had scanned OpenVM using the same procedure it applies to any new project: a plain LLM with a generic prompt, followed by that same LLM backed by the team’s expert skills. They first ran Opus 4.6 and Codex 5.3, and as soon as Opus 4.7 and Codex 5.4 came out, they repeated the scan with those models.
The results of those four runs were valid but non-exploitable observations. The models labeled several findings as Critical or High with full confidence, and none held up under serious manual review.
The team’s hypothesis was that a zkVM is too complex for an unassisted LLM to understand even with 300,000 or a million tokens of context: the dependencies between modules are far denser than in a typical cryptographic library, where it’s enough to hand each primitive off to a separate subagent reading a bounded folder.
That’s why the team decided to run zkao, its AI auditor with its own context engineering, even breaking its own rule of using it only after a plain LLM had already found a real bug. After more than nine and a half hours of scanning, zkao returned many findings; among them, one stood out immediately: a critical soundness bug in the pairing verification of a guest library.
📌 Note: zkao produced a candidate finding, not a final report. zkSecurity’s human team validated the bug, confirmed it was exploitable, and built the minimal PoC before reporting it to OpenVM.
Background and history
Pairings are the engine behind Groth16, behind PLONK when it uses KZG commitments, and behind BLS signatures. In all three cases, the verifier doesn’t evaluate a single pairing: it evaluates whether a product of several pairings equals one. That single binary answer determines whether a SNARK proof is valid, whether a KZG opening is correct, or whether a signature verifies.
Formally, a pairing is a bilinear map e: G1 × G2 → GT, where G1 and G2 are groups of an elliptic curve and GT is a multiplicative subgroup of an extension field, in this case F(p¹²). Computing a pairing has two stages: first the Miller loop, which evaluates a Miller function and produces an element of F(p¹²); then the final exponentiation, which reduces that element to the correct subgroup of GT.
When a product of several pairings needs to be verified, the circuit can run all the Miller loops separately and multiply their outputs before applying a single final exponentiation. That optimization, very common in efficient implementations, is what opens the door to the bug: if the check that the intermediate result truly belongs to the correct subfield isn’t strict, an attacker can fabricate a value that passes the final check without being the legitimate pairing product.
Technical details and performance
| Bug | AI Severity | OpenVM Severity | Fix Commit | Found By |
|---|---|---|---|---|
| openvm-pairing: missing subfield check on the scaling factor | Critical | Critical | a720e2c | zkao |
This is the first time in this series of zkSecurity experiments that the severity assigned by the AI exactly matches the one confirmed by the maintainer team: both sides rated it as Critical.
flowchart TD
A["Miller loop for each pair (Pi, Qi)"] --> B["Product of the outputs f"]
B --> C{"Subfield check of the scaling factor"}
C -- "no check: bug" --> D["Final exponentiation accepts any f"]
C -- "with check: fix 1.6.0" --> E["Final exponentiation only over valid values"]
D --> F["Malicious prover forges the equality"]
E --> G["Sound verification"]
In pseudocode, the vulnerable verification looked roughly like this:
// Pairing verification WITHOUT the subfield check (vulnerable)
fn verify_pairing_product(f: Fp12) -> bool {
let result = f.final_exponentiation();
result == Fp12::one()
}
The problem is that final_exponentiation() alone doesn’t guarantee that f actually came from a valid product of Miller loops. The fix adds the missing check before accepting the result:
// Fixed verification in OpenVM 1.6.0: validates that the
// scaling factor belongs to the correct subfield
fn verify_pairing_product(f: Fp12, scaling_factor: Fp12) -> bool {
if !scaling_factor.is_in_correct_subfield() {
return false;
}
let result = f.final_exponentiation();
result == Fp12::one()
}
Getting started/trying it out
If your project depends on openvm-pairing, the first step is to confirm which version you’re using and pin it explicitly in your Rust manifest:
[dependencies]
openvm-pairing = "1.6.0"
Then update the dependency and check the version tree. The command is identical on Linux, macOS, and Windows because it runs on Cargo, not on the operating system:
# Linux / macOS / Windows (PowerShell or terminal)
cargo update -p openvm-pairing --precise 1.6.0
cargo tree -p openvm-pairing
To confirm you’re on the patched version, check that the output of cargo tree shows openvm-pairing v1.6.0 or higher. If your Cargo.lock still points to a version before commit a720e2c, your build remains exposed.
Impact and analysis
The bug itself is narrow, but the process that found it leaves a broader lesson for anyone auditing complex cryptographic code. Splitting a zkVM into folders and auditing each primitive in isolation isn’t enough, because security doesn’t always compose.
💭 Key point: you can have a provably secure module A and a provably secure module B whose composition A+B stops being secure. That’s why auditing each primitive separately doesn’t catch this kind of bug in a zkVM.
According to zkSecurity, what a subagent should produce when reviewing a dense module isn’t a list of bugs, but reusable knowledge: what that module assumes, what it delegates to its caller, and what invariant it takes for granted without checking. Representing that knowledge well is the hard part: too short, and it loses the implementation detail where the bug lives; too long, and it saturates the main agent’s context before it can be combined with the rest.
What’s next
zkSecurity plans to continue this series with more complex targets, now that the hypothesis that a zkVM requires tools with dedicated context engineering, not just an LLM with more context tokens, has been validated with a real bug.
⚠️ Heads up: if you maintain a library that does pairing checks, explicitly verify that the subgroup or subfield check applies to every intermediate element, not just the final result of the exponentiation.
For the zkVM ecosystem in general, the episode serves as a warning: any guest library that adds pairings, KZG, or BLS signatures on top of a modular framework like OpenVM should add this check to its standard audit checklist, rather than assuming the base framework’s security covers the libraries that run on top of it.
📖 Summary on Telegram: View summary
Try it yourself: clone the OpenVM repository, pin openvm-pairing = "1.6.0" in your Cargo.toml, and run cargo tree -p openvm-pairing to confirm you’re already on the patched version.
Frequently Asked Questions
What is a zkVM, and why does this bug matter?
A zkVM is a virtual machine that generates a zero-knowledge proof that it correctly executed a program, without revealing the input data. OpenVM is a framework for assembling modular zkVMs in Rust; the bug isn’t in the core of the proving system, but in openvm-pairing, a guest library that any project can use for pairing operations.
What is CVE-2026-46669, and which version fixes it?
CVE-2026-46669 identifies the soundness flaw in the pairing check of openvm-pairing. It was fixed in commit a720e2c, included in OpenVM 1.6.0.
Does the bug affect OpenVM’s proving system in general?
No. According to zkSecurity, it isn’t a soundness bug in the zkVM’s proving system itself. It only affects the code of projects that use the openvm-pairing library to verify pairings, for example in Groth16, KZG, or BLS.
How could a malicious prover exploit the flaw?
Because the check that the scaling factor belongs to the correct subfield was missing, an attacker could construct a value that passed the final exponentiation without being the actual product of the Miller loops, causing the verification to accept a false pairing equality.
How did AI take part in finding the bug?
zkao, zkSecurity’s AI auditor, scanned the OpenVM code for more than nine and a half hours and returned the finding as a candidate. Months earlier, scans with plain LLMs like Opus 4.6, Codex 5.3, Opus 4.7, and Codex 5.4 had not detected a real exploitable bug.
Which projects are affected?
According to zkSecurity, all known partners building on OpenVM had already updated to version 1.6.0 by the time the finding was published.
References
- zkSecurity: AI meets Cryptography 2, What AI Found in OpenVM’s zkVM: original publication with the full analysis of the bug and the PoC.
- NVD: CVE-2026-46669: official record of the vulnerability in the U.S. National Vulnerability Database.
- GitHub: openvm-org/openvm: official repository of the OpenVM framework and its openvm-pairing library.
- Wikipedia: Pairing-based cryptography: general background on bilinear pairings and their cryptographic applications.
📱 Do you like this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day.
Imagen destacada: Foto de Jake Walker en Unsplash
0 Comments