# Known limits
**What we have.** Soundness is proved, in Lean, for the verifier written in Lean: it accepts a proof
of a false claim with probability at most about 2^-124 per hash query (with a floor of about
2^-101), in the random-oracle model, for [exact-match](terms.md) claims; under the [clipped-gap
policy](terms.md) that serving uses, Lean proves only that the reply meets that policy (below). The
C++ verifier and the CUDA prover are written to follow the same protocol and are tested against it,
not themselves proved. Completeness, where the model has a result, and zero knowledge are proved in
Lean for the protocol, at full size and at the exact-match policy, each up to a small stated error,
with fresh random masks; after compiling, both are on one leaf table. The honest prover in these
theorems is a Lean definition, not the CUDA prover: nothing is proved for the programs. Where the
model has no result on a request, completeness says nothing. So, for a prototype, we have reasonably
high trust in what the system produces: soundness, which matters most for a verifier, is
machine-checked for the Lean verifier, and the C++ verifier is tested against it. We expect, rather
than know, that the programs hide the weights, within the bounds told under [Weight
hiding](weight-hiding.md); few attacks were sought, and no test on the released programs hunts for a
leak. More: [What is proved](what-is-proved.md).
**What follows is what is left:** what a user of FlashProve can trip over or be misled by. The known
limits at length: [docs/limits.md](../docs/limits.md),
[docs/limits-further.md](../docs/limits-further.md), [one table](../docs/limits-all-rows.tsv) and,
for Lean, [lean/LIMITS.md](../lean/LIMITS.md). The list may be incomplete: the last reviews behind
it found new limits.
## If you check replies
**The provider chooses the model.** A yes does not say whose weights they are, that the shipped
committer made the commitment, or which real model it is. A provider can commit to any model it
likes, a small or degenerate one included, and a scale file within the admitted range can switch off
model blocks, up to all of them (worked out by hand). Build `cp_verify` yourself. [Get the
commitment ID from a party you trust](verify-a-proof.md#what-you-need-first), never from the server
you check or from files sent with a proof, and hold the three configuration files as you hold the
ID.
**`WITHIN LIMITS` does not say the reply is the model's.** Under the looser clipped-gap policy an
accept says only that the output's token costs meet the policy's limits at the weights the extractor
reads off the commitment, which by itself bars little: with loose limits, close scores or flat
(all-zero) weights almost any output passes. Lean's theorem for that policy ([the theorem under a
policy](what-is-proved.md)) says only that the output meets it, and the argument on paper (below)
covers its equations only in part. The policy's mean and count of token costs run over one output
that the prover picks, and prompt tokens are not scored. No run shows the clipped-gap policy's own
limits rejecting anything at a verifier (replies that missed the limits got no proof from the honest
prover). Both kinds of accept exit with code 0, so read the last line. Yet clipped gap is the
expected use: of the 96 honest replies scored† from each of two stock engines none would pass exact
match, and 28 of 32 replies† with one token swapped for the model's second choice met limits fitted
to honest replies (both scored by the integer-model package, in Python, not proved). The project
recommends no limits: if you cannot assess them, accept only exact match.
**A pass covers only checked tokens.** Only the named output positions are covered, and a reply
stopped short verifies as a shorter one. [Build the request file yourself](verify-a-proof.md),
compare the `covered:` line with your range, and check that the reply ends with an end token: [exit
code 0 alone does not mean accepted](verify-a-proof.md#when-to-accept). A proof names no client or
time. A hostile file can exhaust memory or time: treat a call that hangs or is killed as not
accepted.
**Proofs are about the integer model,** quantized and fixed-point. How far its outputs track the
original floating-point model's is a measurement, not part of any proof (in one measurement†, with a
Python implementation of the integer model, the top token differed at 6.81 % of positions), and no
trained weights are shown to meet the protocol's range conditions (a count† outside Lean, by the
integer-model package's own range checks, found none failing on 544 of 544 windows, at a public
model's 4-bit weights).
**Printed bits are not a security level.** The C++ programs' printed "bits" are their own
accounting, not a theorem: the weakest round prints 128.00 bits with up to 32 bits of proof of work
counted by assumption (96.23 without), no margin, about 6 bits fewer per batch of 64. That
accounting rests on two published results that are cited, not re-derived for these programs (a
proximity-test soundness analysis and a theorem on codes), and on an argument on paper, in a
technical report that is not public and not refereed, whose conditions no complete set of parameters
of any size is known to meet and which a configuration file can fail while still passing the
verifier's checks. The proved figure is the Lean theorem's, about 124 bits per hash query (with a
floor of about 2^-101), not the programs' printed one. Lean also has theorems on single rounds,
which are not the chance of a forgery: [What is proved](what-is-proved.md). Two published attacks on
Fiat–Shamir transformations (ePrint 2025/118; 2026/1838) have not been examined against this
protocol or these programs.
## If you serve a model
**The provider's duties.** Hiding depends on what the provider does: [the thirteen
duties](serving.md#the-providers-duties). Keeping them all does not show that the weights are
hidden: little has looked for a leak or an attack, and any real model is outside what we judge of
hiding (only one of five tests of hiding has run on any). [The example front
end](../integrations/LIMITS.md#what-the-integration-reveals) does not meet [duty 11](../docs/limits.md#the-conditions), so a server with it alone in front is outside what we judge of hiding. By that duty a front
end asks only about served replies, over ranges that it sets. What breaking a duty can cost, what is
visible by design, and what tests of lost masks found: [Weight hiding](weight-hiding.md).
**The prover does not check its own proofs.** `cp_prove` releases a batch's responses once all of
them are staged. After a hardware fault or a bug, a response that `cp_verify` rejects can hold
unmasked values. The vLLM integration that ships hands on no proof, and nothing of its response, before `cp_verify` has passed every proof of its batch (narrow exceptions, none open to a client: [integrations/LIMITS.md](../integrations/LIMITS.md#narrow-exceptions)); `driver/measure_rate.py` reports no rate, and an example script shows no answer, before `cp_verify` has accepted the proofs; and a front end of your own must do the like ([Weight hiding](weight-hiding.md);
[docs/limits.md](../docs/limits.md#a-response-that-does-not-verify)).
**A commitment runs out.** At full size and the default setting each leaf table (a masked encoding
of the weights) serves 688,127 batches, by calculation (the same number in Lean's theorems counts
replies, each to one request, not batches: [Weight hiding](weight-hiding.md), "The counter").
Nothing warns you before that limit, and a commitment cannot be extended. A new commitment has a new
ID, and [no proof links the two](verify-a-proof.md#when-the-commitment-id-changes). Compare the size
of each leaf table's `count.bin` with `cp_commit`'s line `the limit:`.
**64 per batch is not to be relied on with an 80 GB card:** all eight recorded attempts on synthetic
weights, six of them†, failed with no proof, cause unknown. On a public model's 4-bit weights all
three services at 64 ended normally (960 of 960 proofs accepted), but the GPU reached 80,629 of its
81,559 MiB. The two kinds of run differ in more than their weights ([Performance](performance.md)).
**The example server and `sudo`.** The example front end's server, started with `sudo`, [can be led
to send out files that only root may read](../integrations/LIMITS.md#above-a-public-directory). Run
nothing here as root.
**Also:** [after a fault, do not restart the service](../docs/limits.md#after-a-fault); [a killed
`cp_commit` can leave the seed behind](../docs/limits.md#a-killed-cp-commit); [`cp_prove -spool`
removes files from the directory that you give
it](../docs/limits.md#the-spool-directory-is-cleaned); [`cp_prove` does not tell one commitment from
another](../docs/limits.md#one-commitment-one-directory).
## How far the tests go
Many of the tests and readings that this repository reports were not made on the released programs,
but on programs built from [other sources](../docs/glossary.md#other-sources); some, the tests with
deliberately cheating provers among them, on programs that also held test-only code. The compiled
programs differ too; the machine code that differs is not shown equivalent and has not been read.
Two of the five tests of weight hiding, the first and the fifth, have run on the released programs,
the first at full size too, the fifth at small size, and one of its two audits, the audit of a
commitment, at full size too, on none but honest commitments, in the timed runs. The second, the
third and the fourth ran on other programs ([Weight hiding](weight-hiding.md)). On the released
programs `examples/maverick/run.sh` has gone once through all the stages of `run.sh all`, at full
size, on a real model's public weight files: the run reported that the five values in
`examples/maverick/expected.txt` came out the same, byte for byte, and that `cp_verify` ended with
`YES`; its build stage was skipped, its download stage fetched nothing, and the tools that its stage
`needs` asks for were installed beforehand, so building, downloading and installing the tools were
not exercised. What ran on which programs: [what was run on these programs, and what was
not](../docs/limits.md#what-was-run-on-these-programs). There has been no independent security
audit. AI systems, under human direction, wrote nearly all of the software, the Lean proofs and the
documentation, and ran the tests reported here.
**Tests against lying proofs are narrow.** At full size 57 lying proofs were tried (24 made for a
false claim, on one request; 33 carrying a planted lie, for the true replies to three requests):
none was accepted by either verifier (the Lean verifier's parser refused 3 outright, and a
no-verdict is not a rejection). Calls on the 57 lying proofs are among those of the Lean verifier's
reference run; what is known of the command and the parser there stands in row L9 of [the
table](../docs/limits-all-rows.tsv). Whether the command and the parser at the Lean verifier's calls
told in this paragraph outside that run were this repository's has not been compared. No proof of a
false claim that also carries such a planted lie has been put to either verifier at full size in the
runs this page reports. Those 57 trials were on one commitment, synthetic weights, exact match, lies
of a few kinds, in 22 of the 57 none aimed at the lookup checks (by report), every rejection at one
verifier step; all 24 were refused at one and the same place inside that step. None was put to
`lean-verify` under a clipped-gap policy. No prover was built to search for forgeries; apart from
one cheating committer's false leaf file (a false copy's file) at full size, tried once through a
form of the executable `verify` that is not in this repository and through a C++ verifier built from
other sources, and rejected by both, in `verify` at that file's own check, by report
([lean/GAPS.md](../lean/GAPS.md), 14 (i) and 33), no malicious committer ran at full size at the
default setting; and other attacks were mostly at small sizes. That file was tried over Lean files
that differ from this repository's; no such trial has been made on this repository's files. The test
programs that made the lying proofs are not in this repository, so those trials cannot be repeated
from it. Most of the Lean verifier's runs on honest proofs cannot be repeated from this repository
either: except for the two stored full-size examples' proofs, the honest proofs are not in it.
**The two verifiers are close, not identical.** The Lean verifier runs sixteen tests of its own on
the public files and refuses where one fails; those tests are proved, in a side library, to pass at
the library's own copy of the full-size public files; that this repository's files equal that copy
is checked by a script (`lean/scripts/check_stored_examples.py`), not by a theorem. Elsewhere the
theorem may hold only because the Lean verifier refuses. A parameter file can pass the C++ verifier
and yet give less soundness than the figures it prints. More: [lean/GAPS.md](../lean/GAPS.md),
[lean/LIMITS.md](../lean/LIMITS.md).
**The `blocks/` library can be misused.** A program that calls it directly, outside the contract in
`blocks/chain/closed.h`, can be made to accept a false claim; at least one such way is known to work
at the programs' full configured target (128 bits by their own accounting), at toy size. That the
C++ verifier is not exposed rests on reading its source, not on a test. Check proofs with the C++
verifier or the Lean verifier; do not build a verifier on `blocks/` without keeping the contract.
**Altered stored tables can verify.** A committer that alters part of the table it
stores beside a commitment can still have its proofs accepted by the C++ verifier (observed with up
to 131,072 of 8,388,608 positions altered, on a small configuration, and 24 of 24 tries at 1 to 256
positions; on test builds). In every such case the table, by separate programs' counts of the changed columns (BLAKE2b-256 taken as collision-free), not machine-checked and not by a proof, still lay far inside the distance at which it decodes to the honest weights alone, so that the accepted claim was the honest run's own: a yes vouches for the claim, not for every byte stored.
**Proofs are malleable.** Some bytes of an honest proof, or of a commitment's files, can be altered
and the proof still accepted, for the same true claim; non-malleability is not claimed, and an
accept does not show that this exact file came from the prover. The Lean files and the parser's
sources in this repository differ from those of the Lean verifier's calls told in this paragraph. Of
41 altered inputs put to the Lean verifier's executable form directly, on parsed files, not through
the released command, nine were accepted (all altering Merkle-path entries of the parsed proof file
that the verifier's walk does not take; the claim untouched). The two verifiers can part on such
altered files: at three of four altered copies of one honest proof, the claim unchanged and true,
the Lean verifier accepted the parsed file where the C++ verifier rejected the raw one (one run†).
† Not on the released programs: [which kinds of result these
are](../docs/limits.md#on-other-programs).