This project makes a security claim, so an unexamined implementation is worth very little. Four soundness bugs have already been found and fixed by the authors, in their own code:
-
A self-certifying receipt. A receipt carries its own identity set, and verification originally checked it against that set. Anyone could mint keys, sign contributions, and produce a receipt that verified perfectly. Fixed by requiring an independently supplied PKI; the tool now refuses to give a verdict without one.
-
Unbounded allocation from a length prefix. The decoder allocated on counts read straight from untrusted input, so a few dozen hostile bytes could abort the verifier before a single signature was checked. Fixed by bounding every count against the bytes actually present.
-
A tie-break key derived from arrival order. The default tie key came from the order contributions arrived, so two replicas holding the same set could break an exact score tie differently and produce different aggregates. That defeats the whole point: the result has to be a function of the set. Tie keys are now caller-supplied and stable, and a call that cannot obtain one refuses rather than guessing.
-
An integer overflow reachable from untrusted wire bytes.
encode()bounds values to the Q16.16 range, but a contribution built directly from rawi64-- which is what decoding a receipt does -- reached the aggregator'si128accumulators unbounded. The failure had two stages, and the first one hid the second: at+/-2^62each squared coordinate difference still fits, so the distance function returned cleanly, and the score accumulator then summed four of them pasti128::MAX. Guarding the distance function alone would have moved the fault one line down. Fixed by bounding raw values at both entry points -- the wire decoder and the aggregator's own validation -- which makes every accumulator on the path safe by construction rather than by audit. A panic here is a denial of service; under a consumer build with overflow checks disabled it would instead have been a silently wrong selection, which is worse.
Finding your own bugs is not the same as being audited. There has been no independent security review. If you are considering this for anything that matters, please assume there are more and go looking.
An independent adversarial review across six lenses produced sixty findings. The fixed ones are recorded in AUDIT.md with the commit that closed each. Open findings are tracked as issues; a small number are held back until they are closed, because publishing an unfixed weakness in a system people may be running hands an attacker a recipe. That set is not enumerated, and saying so is the honest half of the decision.
Open a GitHub security advisory, or a public issue if the finding is not sensitive. There is no bug bounty. Expect an acknowledgement and an honest assessment, including "you are right and we shipped that" where it applies -- the project's own discipline is that a confirmed defect is published as loudly as a result.
- The verifier reaching VERIFIED on a receipt it should reject.
- Any input causing a panic, abort, hang, or unbounded allocation in
decodeorverify. - Any divergence in the aggregate across architectures, compilers, or optimisation levels. A single reproducible instance refutes the central claim and is the most valuable thing you could send.
- Attribution failures: a participant that misbehaves without the receipt recording it, or a proof that convicts an honest participant.
Reporting these is welcome but they are known scope limits, documented in the README:
- Sybil resistance is delegated to whatever issues the PKI.
- Withholding. A valid receipt proves honest computation over the set it showed you,
not that it showed you everything it held. Detecting withholding requires comparing the
state root against an independently obtained one, and the verifier performs that
comparison for you:
acfa-verify --expect-state-root <64-hex>exits 1 on a mismatch. Untilrust-08this paragraph named a mitigation the shipped tool had no way to accept. - Round finality is provided by
acfa-finalityand is IN scope: a certificate that verifies on evidence it should reject, or a synchrony violation that does not produce transferable evidence, are both reportable. What is NOT claimed is liveness under an adversarial network -- the design halts visibly instead of proceeding. - Krum admits coordinate-concentrated attacks that stay inside the honest spread. That
is a property of the imported rule; use
bulyan, which needsn >= 4f+3. - Values outside Q16.16 range are refused, not handled. That is deliberate: saturating would make the result depend on which replica saturated first.
Up to f of n participants are Byzantine and may send anything, to anyone, in any order,
including different values to different parties. The network may reorder and duplicate.
Verification assumes the checker independently knows which public keys belong to the
deployment and what f is; supplying either from the receipt itself is circular and the
tooling refuses to do it.