110 tok/s, 19MB Proofs, 17-Second CPU Verification: Anthropic Open-Sources FlashProve and Turns Model Substitution into a Math Problem
110 tok/s, 19MB Proofs, 17-Second CPU Verification: Anthropic Open-Sources FlashProve and Turns Model Substitution into a Math Problem
Part One: You Paid for the Flagship. Is the Flagship What Ran?
On November 2, 2026, Anthropic’s Frontier Red Team published a research post with a plain title: “Progress on provable inference for large language models.” On November 4 the accompanying code went open source under the name FlashProve.
The problem it addresses fits in one sentence:
When you call a model, how do you prove that the text returned to you was actually generated by the model you paid for?
This is no longer theoretical. Three scenarios happen every day.
First, model substitution. A third-party routing platform forwards requests, and to save cost swaps the user’s specified high-performance model for a cheaper small one. The answers may still look decent; the user just thinks “it seems a bit dumb today” and has no evidence to offer.
Second, output tampering. An inference server is compromised, and an attacker mixes malicious content into an otherwise normal token stream — say, steering a reply toward code the user will execute.
Third, evaluation mismatch. An independent evaluator runs safety assessments before and after a model release, but if they can’t confirm the model under test is the model actually being served, the entire report’s credibility is zero.
Anthropic wrote one line that lands hard:
As models become capable of improving and deploying themselves, a more advanced model could run in place of the one that was authorized and present itself as the weaker model, so as to avoid the classifiers and monitoring that would apply to it.
That is the core of what “provable inference” (PI) must solve: let a user with limited compute and no knowledge of the weights verify that a response came from the specific model they requested.
The traditional route is exactly one thing: get the weights and re-run the model yourself. But frontier weights are each company’s crown jewels, and nobody — not a normal user, not even most evaluators — can afford the machine room for a 400-billion-parameter model.
So the answer is cryptography. Specifically, a zkSNARK — a zero-knowledge succinct argument of knowledge: the prover convinces the verifier, via a short certificate, that a computation was executed correctly. The certificate is zero-knowledge (revealing nothing about weights, intermediate values, or which experts ran) and succinct (verification costs far less than re-running the computation).
How far did Anthropic push it? Three numbers:
- 110 tokens/second of proof generation on a single H100
- ~19MB per 1,024-token request (precisely 18.78MB)
- 17 seconds to verify on a 24-thread CPU, no GPU needed
Against the historical baseline, those numbers are absurd. The only prior open system to prove the same model (Llama 4 Maverick) was VerInf: 0.02 tokens/second — 14.3 hours for 1,000 tokens, a 93.6GB proof, and nearly a day to verify.
110 divided by 0.02 is 5,500x.
Part Two: Three Steps — Fingerprint, Prove, Verify
FlashProve’s flow is remarkably clean: two parties (the provider is the prover, the user is the verifier) and two phases.
┌─────────────────────────────────────────────────────────────────────┐
│ OFFLINE PHASE (once per model) │
│ │
│ Model weights (400B MoE, 4-bit, ~232GB) │
│ │ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ Hiding commitment │ ← 39 min on 1× H100 │
│ │ (short fingerprint, │ good for ~16M requests │
│ │ weights stay hidden) │ client downloads ~6.5MB total │
│ └────────────┬───────────────┘ │
└────────────────┼─────────────────────────────────────────────────────┘
│ published commitment
▼
┌─────────────────────────────────────────────────────────────────────┐
│ ONLINE PHASE (per request) │
│ │
│ prompt (1024-token window) + output tokens │
│ │ │
│ ▼ │
│ ┌────────────────────────────┐ ┌──────────────────────────┐ │
│ │ Prover (1× H100) │ │ Proof file ~18.78 MB │ │
│ │ · record computation │───▶│ · hides weights │ │
│ │ trace (matmul, round, │ │ · hides intermediate │ │
│ │ nonlinearity) │ │ values │ │
│ │ · prove trace used │ │ · hides which experts │ │
│ │ committed weights │ │ actually ran │ │
│ │ · batch up to 64 requests │ └────────────┬─────────────┘ │
│ └────────────────────────────┘ │ │
└──────────────────────────────────────────────────┼───────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ VERIFIER (user side) │
│ │
│ inputs: commitment + prompt + reply + proof │
│ │ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ cp_verify (C++, fast) │ 17 s on 24 threads │
│ │ → accept / reject │ 21 s on 1 thread │
│ │ │ no GPU, no weights │
│ │ lean-verify (Lean, slow) │ provably accepts only what the │
│ │ → formally guaranteed │ soundness theorem accepts │
│ └────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Step one, offline fingerprinting. Before a model goes live, the provider spends about 39 minutes on a single H100 producing a cryptographic commitment to the weights — a digital fingerprint. The fingerprint pins down one exact set of weights (change a parameter and it no longer matches), while making it impossible to reconstruct the weights from the fingerprint. The commitment is good for roughly 16 million requests before it must be regenerated. The client downloads the commitment, offline proof, and a linking proof — about 6.5MB total.
Step two, online proving. For each request, the prover records the computation trace (matrix multiplies, rounding, nonlinearities), then produces a proof showing those operations used the committed weights and produced exactly this output. A batch handles up to 64 requests, with partial proof steps for each request running concurrently on the GPU and the whole batch sharing the query data used to check the commitment.
Step three, client verification. Holding the commitment, the prompt, the reply, and the proof, the user runs the verifier on an ordinary CPU and gets a verdict in 17 seconds. No weights, no GPU.
In code, this flow writes itself as three very plain functions:
import hashlib
import secrets
def offline_commit(weights_bytes: bytes, nonce: bytes | None = None) -> dict:
"""
Offline phase, simplified: build a hiding commitment to the weights.
Real FlashProve uses a zkSNARK-friendly polynomial commitment so that
later proofs can *open* specific entries without revealing the rest.
Here we only model the hiding + binding properties.
"""
if nonce is None:
nonce = secrets.token_bytes(32)
# A hiding commitment = hash(nonce || weights).
# Without the nonce you cannot brute-force weights from the digest,
# and you cannot find two (nonce, weights) pairs with the same digest.
digest = hashlib.blake2b(nonce + weights_bytes, digest_size=32).hexdigest()
return {
"commitment": digest,
"nonce_kept_by_prover": True, # verifier never sees this
"weights_hidden": True,
"size_bytes": 32,
}
def online_prove(commitment: str, prompt: str, output_tokens: list[int],
computation_trace: dict) -> dict:
"""
Online phase, simplified: assert that the committed model, run on this
prompt, produces exactly these tokens.
A real proof is ~18.78MB of zkSNARK data; here we only model what the
proof *claims* and what it *hides*.
"""
claims = {
"statement": "committed_model(prompt) == output_tokens",
"commitment": commitment,
"checked_tokens": len(output_tokens),
}
hides = {
"weights": True,
"intermediate_values": True,
"expert_selection_per_layer": True,
"architecture": False, # explicitly NOT hidden
}
return {
"claims": claims,
"hides": hides,
"proof_size_mb": 18.78,
"prover_gpu": "1x H100",
"prover_seconds_per_batch_of_24": 223,
}
def verify(commitment: str, prompt: str, output_tokens: list[int],
proof: dict) -> dict:
"""
Verifier side: cheap, no GPU, no weights.
"""
ok = (
proof["claims"]["commitment"] == commitment
and proof["claims"]["statement"] == "committed_model(prompt) == output_tokens"
)
return {
"accepted": ok,
"verify_seconds_24_threads": 17,
"verify_seconds_1_thread": 21,
"gpu_required": False,
"weights_required": False,
}
if __name__ == "__main__":
fake_weights = b"\x00" * 1024 # stand-in for ~232GB of quantized weights
c = offline_commit(fake_weights)
print(f"commitment: {c['commitment'][:16]}... ({c['size_bytes']} bytes)")
p = online_prove(c["commitment"], "explain zkSNARKs", [1, 2, 3], {})
print(f"proof size: {p['proof_size_mb']} MB, hides weights: {p['hides']['weights']}")
v = verify(c["commitment"], "explain zkSNARKs", [1, 2, 3], p)
print(f"accepted: {v['accepted']}, GPU needed: {v['gpu_required']}")
The code is deliberately shallow, but it draws the information flow clearly: the commitment is public, the proof ships with the request, verification is fully offline. The only link between the three is a 32-byte digest. Which is exactly why an attacker who fully controls the inference server still cannot forge a valid proof for the wrong weights — unless they break BLAKE2.
Part Three: Where 110 tok/s Comes From — Three Engineering Moves
The cryptographic protocol itself is public and textbook-level. The hard part is compressing it onto a single H100. Anthropic’s engineering has three moves, each worth separate attention.
Move one: make proving work track the experts that actually ran.
Llama 4 Maverick is a mixture of experts. Each MoE layer selects 1 of 128 routed experts, plus 1 shared expert. So each token activates only about 17B of the 400B parameters.
If a proof had to cover all 400B parameters, it would never run. FlashProve instead records only the experts that actually executed in the computation trace, so proving work is proportional to active parameters (17B) rather than total parameters (400B) — while additionally checking that the router selected the correct expert.
There’s a subtlety: the proof must hide which experts were selected. Expert selection is itself commercial IP; leaking it leaks the routing policy. So the system must simultaneously satisfy “prove only the active portion” and “reveal nothing about which portion was active.”
# Why MoE sparsity is the single biggest lever on proving cost
def moe_proving_cost(total_params_b=400, active_per_token_b=17,
n_experts_per_layer=128, layers=48):
"""
Prover work is proportional to the experts that actually ran,
not to the full parameter count.
"""
dense_relative = total_params_b / active_per_token_b
# Per layer: 1 of 128 routed experts + 1 shared expert
experts_active_per_layer = 2
sparsity_ratio = experts_active_per_layer / (n_experts_per_layer + 1)
return {
"total params (B)": total_params_b,
"active per token (B)": active_per_token_b,
"dense-equivalent slowdown": f"{dense_relative:.1f}x",
"experts active per layer": f"{experts_active_per_layer} of {n_experts_per_layer + 1}",
"sparsity ratio": f"{sparsity_ratio:.1%}",
"layers": layers,
"consequence": "proving cost tracks 17B, not 400B",
"extra burden": "must also prove router picked the right expert, "
"while hiding which expert was picked",
}
for k, v in moe_proving_cost().items():
print(f" {k:28s} {v}")
The leverage on this move is 23.5x. In other words, if FlashProve weren’t purpose-built for MoE, its throughput would collapse to single-digit tokens per second.
Move two: slice the model, feed the GPU from host memory.
The 4-bit quantized 400B model weighs about 232GB. A single H100 has 80GB of VRAM. It doesn’t fit.
FlashProve’s answer: weights live in ordinary host memory and are streamed into the GPU one segment of layers at a time, proven segment by segment. The net result — a single 80GB GPU with about 290GB of host memory beside it suffices.
That number matters because it means the system needs no exotic hardware: pull one card out of a standard 8-GPU node, max out the memory, and you have a proving server. It is not something only Anthropic can run.
Move three: prove post-hoc, in batches, opening the commitment once per batch.
This move is what takes throughput from 17.6 to 110.
The system doesn’t prove one request at a time. It reads the whole finished text in one parallel pass (like prefill), then proves a batch together, and opens the weight commitment once per batch rather than once per request.
The data: proving a single request takes about 58 seconds; proving a batch of 24 takes about 223 seconds total. Counting all tokens in the request windows, throughput rises from 17.6 to 110 tokens/second.
# Batching: the single change that took throughput from 17.6 to 110 tok/s
def batching_throughput(window_tokens=1024):
single = {
"requests": 1,
"total_seconds": 58,
"tokens": window_tokens,
}
batched = {
"requests": 24,
"total_seconds": 223,
"tokens": 24 * window_tokens,
}
single_tps = single["tokens"] / single["total_seconds"]
batched_tps = batched["tokens"] / batched["total_seconds"]
return {
"single request (s)": single["total_seconds"],
"single throughput (tok/s)": f"{single_tps:.1f}",
"batch of 24 (s)": batched["total_seconds"],
"batched throughput (tok/s)": f"{batched_tps:.1f}",
"speedup": f"{batched_tps / single_tps:.1f}x",
"per-request amortized (s)": f"{batched['total_seconds'] / 24:.1f}",
}
for k, v in batching_throughput().items():
print(f" {k:28s} {v}")
Batching compresses “58 seconds per request” into “9.3 seconds per request” — not through a faster algorithm, but by spreading fixed overhead (opening the commitment, preparing query data) across 24 requests. Same principle as GPU inference batching, except here the “fixed overhead” is a cryptographic commitment.
Part Four: A More Revealing Comparison — From 0.001 to 110
A single number means little. Put it into the zkML history and it speaks.
# The zkML throughput curve, in tokens per second
def zkml_history():
rows = [
("2017 SafetyNets", "MNIST 2-conv CNN", None, "verified, not throughput"),
("2021 zkCNN", "VGG-16 (15M)", None, "88.3 s per proof"),
("2024 ZKML GPT-2", "117M", 0.006, "159 s per token"),
("2024 Modulus GPT-2 XL", "1.5B", 0.0, "over 90 hours"),
("2023 EZKL", "1M GPT-style", 0.001, "966 s per token"),
("2024 zkLLM", "LLaMA-2-13B", 2.5, "batch, 2048-token input"),
("2025 zkGPT", "GPT-2", 0.05, "21.8 s per token"),
("2025 zkPyTorch", "Llama-3 8B", 0.007, "150 s per token, 1 CPU core"),
("2025 ZKTorch", "GPT-J 6B", 0.001, "~20 min"),
("2026 VerInf", "Llama 4 Maverick 400B", 0.02, "14.3 h for 1k tokens, 93.6GB proof"),
("2026 DeepProve", "Gemma 3", 1.4, "2.9 tok/s on GPT-2, 31 distributed"),
("2026 SASH prover", "Llama 3.1 8B", 1.25, "single H100"),
("2026 FlashProve", "Llama 4 Maverick 400B", 110.0, "1x H100, 19MB proof, 17s verify"),
]
print(f"{'System':<26}{'Model':<28}{'tok/s':<10}{'Note'}")
print("-" * 104)
for name, model, tps, note in rows:
tps_s = f"{tps}" if tps is not None else "n/a"
print(f"{name:<26}{model:<28}{tps_s:<10}{note}")
zkml_history()
print("\nFlashProve vs VerInf on the SAME model (Llama 4 Maverick):")
print(f" throughput: 110 / 0.02 = {110 / 0.02:.0f}x")
print(f" proof size: 93.6 GB / 0.019 GB = {93.6 / 0.019:.0f}x smaller")
print(f" verify time: ~1 day -> 17 s")
What matters in this table isn’t the 110. It’s two curves.
First curve: model scale is growing, and proving throughput is growing too. In 2024, zkLLM proved a 13B model at 2.5 tok/s. In 2026, FlashProve proves a 400B model (17B active) at 110 tok/s. Parameters up 30x, speed up 44x. That’s rare in cryptographic engineering — usually scale goes up and overhead explodes.
Second curve: head-to-head on the same model is the only hard metric. VerInf and FlashProve prove the same Llama 4 Maverick: 0.02 versus 110, a 5,500x gap; proof size 93.6GB versus 19MB, 4,900x smaller; verification from nearly a day to 17 seconds.
Anthropic’s own framing is restrained but loaded:
These numbers speak to the precipitous progress of provable inference in the past years. They should update our priors on what it is possible to prove in zero-knowledge.
The subtext: the industry consensus that “zero-knowledge proofs of large models are engineering-infeasible” now needs revision.
Part Five: The Price — 70 to 95 Times the GPU Time
Every technology has a bill. FlashProve’s is clear:
Proof generation requires about 70 to 95 times the GPU time of ordinary vLLM inference.
The measurement: running the FP8 release of the same model on eight H100s with vLLM, per GPU, proving a batch of 24 requests takes about 72x longer than vLLM takes to process the same batch (66 to 77 across runs); at the highest load tested, the multiple rises to about 94x.
# What 70-95x GPU time means in practice
def proving_overhead(overhead_low=70, overhead_high=95,
h100_hourly_usd=2.0, tokens=1_000_000):
"""
Translate the GPU-time multiplier into money per million tokens.
"""
# Assume vLLM serves ~2000 tok/s per H100 for a 17B-active MoE at high load
vllm_tps = 2000
base_gpu_hours = tokens / vllm_tps / 3600
rows = []
for label, mult in [("proving, low end", overhead_low),
("proving, high end", overhead_high)]:
gpu_hours = base_gpu_hours * mult
cost = gpu_hours * h100_hourly_usd
rows.append((label, f"{gpu_hours:.1f}", f"${cost:.2f}"))
print(f"{'Case':<22}{'GPU-hours / 1M tok':<22}{'Cost @ $2/h'}")
print("-" * 62)
for label, gh, cost in rows:
print(f"{label:<22}{gh:<22}{cost}")
print(f"\nBaseline (vLLM only): {base_gpu_hours:.2f} GPU-hours, "
f"${base_gpu_hours * h100_hourly_usd:.2f}")
print("\nInterpretation: proving is not free, but it is a *bounded* multiple —")
print("two orders of magnitude, not six. That is what makes it deployable at all.")
proving_overhead()
What does that cost imply?
It implies FlashProve will not attach a proof to every API call. 110 tok/s of proving against roughly 2,000 tok/s of ordinary inference means full coverage multiplies service cost by 70x or more. Nobody can pay that.
So the realistic usage is sampling: prove only the traffic that needs auditing. Anthropic states the design choice explicitly — they chose post-hoc proving over in-line proving:
In-line solutions are expected to be ultra-fast and may sacrifice security for efficiency, whereas post-hoc solutions are allowed to be slower and may not suffer such a tradeoff. On the other hand, post-hoc proving has limitations: added complexity of recomputation, the delay in getting guarantees about your traffic, and inherent non-determinism.
Worth recording: post-hoc means the guarantee arrives late. Irrelevant for real-time chat; entirely sufficient for compliance auditing. But if your scenario demands every second of traffic be proven in real time, this scheme doesn’t fit.
Several hard constraints must be stated plainly:
- Context is only 1,024 tokens. That’s three orders of magnitude below frontier context windows of hundreds of thousands to millions. Long context, large agent workflows, complex multi-turn interaction — all unsolved.
- Only integer models under greedy decoding. The system proves an exact integer version of the published weights, and asserts each checked token is that model’s greedy choice. A floating-point implementation, or sampled decoding, could depart at some position and no longer match the proved computation.
- One token checked per request. In the tests, each 1,024-token request window checks only the single output token immediately following the prompt. The team also tested checking every token in the reply and found no measurable difference in proving time, but counting only the checked reply tokens, throughput is about 39.1 tok/s.
# Cost model for sampling: full proof coverage is not the intended use
def sampling_policy(total_requests_per_day=10_000_000,
full_proof_cost_multiple=70,
audit_fraction=0.001):
"""
Prove a sample of traffic, not all of it.
"""
audited = int(total_requests_per_day * audit_fraction)
# Cost is only paid on audited requests
effective_multiple = 1 + (full_proof_cost_multiple - 1) * audit_fraction
return {
"daily requests": f"{total_requests_per_day:,}",
"audited requests": f"{audited:,}",
"audit fraction": f"{audit_fraction:.2%}",
"effective cost multiple": f"{effective_multiple:.3f}x",
"detection logic": "random audit makes substitution a losing bet: "
"one caught sample voids the provider's whole claim",
}
for k, v in sampling_policy().items():
print(f" {k:26s} {v}")
This sampling model explains why a 70x overhead is tolerable: not because it’s cheap, but because you don’t pay it every time. Auditors don’t inspect every invoice; they sample randomly. Deterrence holds as long as the sampling is random and the cost of getting caught far exceeds the gain from cheating.
Part Six: The Lean Bet — When Humans Cannot Audit 400MB of Proofs
This is, to my mind, the most interesting layer of the whole thing.
The FlashProve repository’s language breakdown:
| Language | Share |
|---|---|
| Lean 4 (formal proofs) | 95.2% (~398MB) |
| C++ | 1.4% |
| Python | 1.3% |
| CUDA | 0.9% |
In a model inference repository, 95% of the code is mathematical proof. Plus a 114-page protocol specification and a 23-page technical report.
Why? Anthropic’s own explanation:
Cryptographic code is notoriously brittle and error-prone. How can users trust the code we’re releasing today?
The special property of cryptographic code: one sign flipped, one random mask missed, and the entire security edifice collapses. And FlashProve’s core logic was written by Claude — Anthropic concedes: “Claude is good at optimization, but often makes decisions that are hard to evaluate, especially in large complex systems.”
Human experts cannot read hundreds of millions of lines of intermediate derivation. So they changed the approach: don’t audit the middle. Audit only the six top-level security definitions, and let Lean’s compiler kernel check everything in between, line by line.
Three top-level properties (stated in the random oracle model):
- Completeness: an honest prover’s proof is accepted.
- Soundness: any fake proof is rejected, except with negligible probability.
- Zero-knowledge: the proof reveals nothing about the weights beyond what the verifier can infer from the response itself.
As long as those three top-level theorems “say the right thing,” the nearly 400MB of tedious reductions in between can go unread by any human, because Lean’s kernel has already guarded them. It’s a beautiful practice of trust minimization: moving trust from “human review capacity” to “the correctness of a compiler kernel.”
# The three top-level properties, stated as assertions a checker could test
def check_properties():
"""
Informal restatement of what Lean proves. Not a formal proof — just
the shape of the three claims.
"""
properties = {
"completeness": {
"claim": "honest prover's proof is accepted",
"failure mode if violated": "honest providers get wrongly rejected",
"who depends on it": "the provider",
"lean statement": "forall stmt, is_true stmt -> verify (prove stmt) = accept",
},
"soundness": {
"claim": "no valid proof exists for a false statement",
"failure mode if violated": "a cheater can substitute a model and pass",
"who depends on it": "the verifier",
"lean statement": "forall stmt proof, verify proof = accept -> is_true stmt",
},
"zero_knowledge": {
"claim": "proof leaks nothing about weights beyond the reply",
"failure mode if violated": "provider's IP leaks through proofs",
"who depends on it": "the provider",
"lean statement": "exists simulator s.t. proof ~ simulator(commitment, stmt)",
},
}
for name, d in properties.items():
print(f"\n[{name}]")
for k, v in d.items():
print(f" {k:26s}: {v}")
print("\nKey asymmetry:")
print(" soundness depends ONLY on the verifier -> provide a Lean verifier")
print(" zero-knowledge depends ONLY on the prover -> no Lean prover is practical")
print(" completeness depends on BOTH -> test prover against Lean verifier")
check_properties()
But two trust gaps must be stated, and Anthropic lists both.
Gap one: the formal statements themselves need human audit. Lean proves that “these mathematical statements hold,” not that “these statements mean what you intended.” That must be done by people (though far less onerously than auditing proofs). They provide an interactive visualizer, STATEMENTS.html, to help.
Gap two: the executable code must be audited for agreement with the formal protocol. The paper and the fast C++ code are derived from the Lean protocol, but the release does not guarantee that all three match each other.
Hence two verifiers:
cp_verify(C++, fast): for daily use, 17 seconds, no formal guarantee.lean-verify(Lean, slow): Lean proves it accepts only what the soundness theorem accepts.
The logic of this split is worth remembering: soundness depends solely on the verifier. The entire value of “having a proof” is that, if you trust the verifier, you can fully distrust the prover. So the verifier must carry a formal guarantee; the prover need not, because it is the party under suspicion.
Part Seven: It Proves Provenance, Not Correctness
This is the most commonly misread point and deserves its own section.
FlashProve proves “this output came from those weights,” not “this output is right.”
If the committed model itself hallucinates or gives a wrong answer, the proof still passes — because the proof asserts only that “the computation was executed correctly,” not that “the result is semantically correct.”
That distinction looks pedantic but determines where it can and cannot be used:
# What a proof does and does not establish
def proof_scope():
establishes = [
"the reply came from the committed weights",
"the committed weights were run on this exact prompt",
"the checked token is that model's greedy choice",
"no token in the stream was tampered with after generation",
]
does_not_establish = [
"the reply is factually correct",
"the reply is safe",
"the model is the one the provider advertised by name "
"(only that it matches the published commitment)",
"the architecture is hidden (it is NOT hidden)",
"the hardware is not simultaneously training something else",
"side channels (e.g. proving time) leak nothing",
]
print("PROVES:")
for i, s in enumerate(establishes, 1):
print(f" {i}. {s}")
print("\nDOES NOT PROVE:")
for i, s in enumerate(does_not_establish, 1):
print(f" {i}. {s}")
proof_scope()
Note items 3 and 6 in the second list — the two most easily missed boundaries.
Item 3: the proof binds to the commitment, not to the model name. A provider could advertise “we serve Claude” while committing to a different model’s weights — the proof passes, because it only proves agreement with the commitment. For this to mean anything, the commitment must be public, traceable, and bound to the brand claim. That’s a governance problem, not a cryptographic one.
Item 6: Anthropic explicitly scopes side-channel attacks out of this release. In principle, timing differences in proof generation could leak weight information. Not fatal (meaningful signal requires strong measurement capability), but it means “zero-knowledge” has a clear boundary: the proof file leaks nothing, but the timing characteristics of producing it are unconstrained.
Part Eight: Judgment and Boundaries
First, the real significance isn’t “you can verify models” — it’s that trust moves from an organizational problem to a mathematical one. Before this, trusting a provider meant contracts, reputation, audit reports — all social mechanisms. Now there’s a purely technical path: one reply plus a 19MB proof, judged in 17 seconds on a CPU. That path depends on no central authority and requires no provider to hand over a single line of weights. For the deeply opaque model-API supply chain, that’s a structural change.
Second, the real meaning of 5,500x is that a feasibility threshold was crossed. 0.02 tok/s means 14 hours to prove a 1,000-token reply — unusable anywhere, a paper only. 110 tok/s means proving time and generation time are now in the same order of magnitude (still 70 to 95x slower), which is what makes “sample and prove” a viable engineering compromise. Going from unusable to usable-but-expensive is a far more important threshold than going from expensive to cheap.
Third, MoE sparsity is the key lever here, and simultaneously a limitation. The 23.5x leverage comes from “prove only the active experts.” Which also means: for dense models (many 30B and 70B dense releases), that optimization evaporates. Anthropic writes it into the limitations: “our system assumes the served model uses a Mixture-of-Experts architecture.” So this is not a general solution but one customized for the currently dominant architecture — nothing wrong with that, but know the premise before copying.
Fourth, the Lean layer is the answer to a new reality: AI writing cryptographic code. Nearly all the code, papers, tests, comparisons, and review in this project came from Claude; humans settled only six top-level security definitions. Unthinkable two years ago. And it became acceptable precisely because humans gave up auditing the middle and substituted machine-checkable formal proof. That pattern will likely be reused repeatedly: AI handles scale, formal methods handle correctness, humans define what “correct” means.
Fifth, and most needing restraint: this remains a research prototype — in the official phrasing, “intermediate progress, and not a completed research agenda.” 1,024-token context, integer models, greedy decoding, architecture not hidden, side channels out of scope, no formal guarantee on the C++ verifier — any one of those five can be a hard wall in some scenarios. Anthropic also notes this project is separate from its RSP roadmap goal of “a prototype by September 30, 2026” for provable inference (that one signs inference traffic and recomputes on a trusted server); this is the cryptographic route.
A trackable framework: for any “verifiable AI” claim, ask five questions — ① what exactly is proven (provenance, correctness, or compliance)? ② what does verification cost (GPU or CPU, seconds or hours)? ③ what’s covered (all traffic or a sample, how much context)? ④ where is the root of trust (cryptographic assumption, trusted hardware, or some institution’s signature)? ⑤ how far does the formal guarantee reach (protocol, code, or neither)? Five questions and it becomes clear whether “verifiable” is marketing or engineering.
Sources: Anthropic, “Progress on provable inference for large language models” (Frontier Red Team, 2026-11-02), https://www.anthropic.com/research/provable-inference; GitHub
anthropics/FlashProve(Apache 2.0, including prover, C++ fast verifier, Lean verifier, Lean proofs, and STATEMENTS.html); 36Kr, “Anthropic Ends the Shell-AI Era: 17 Seconds to Pull Off the Fake Model’s Mask, 5,000x Faster Verification” (2026-11-04); Zhidongxi, “Verifying AI Identity in 17 Seconds: Anthropic Open-Sources a Provable Inference System That Catches Model Substitution” (2026-11-04); MarkTechPost, “Anthropic Releases FlashProve: Cryptographic Proofs That a Reply Came From Committed Weights” (2026-11-03). The simplified commitment-prove-verify implementation, MoE cost model, batching throughput conversion, GPU cost estimate, and sampling audit model are inference and illustration based on public data, not official figures; Anthropic states this release is a research prototype sharing intermediate progress.