mirror of
https://github.com/saymrwulf/proof-aware-crypto-tooling-agent.git
synced 2026-09-03 19:53:43 +00:00
The deployed consistency verifier implemented the RFC 9162 2.1.4.2 bit-navigation loop but its final return checked only the two reconstructed roots, omitting the terminal condition that the new-size navigation counter reach zero. That condition couples the consumed proof length to the claimed tree sizes; without it, a valid proof for one transition verifies under a lied (power-of-two) old size. Flagship: a valid 2->3 proof is accepted under the false claim 1->3 with the size-2 root. Fix: add `and sn == 0` to the final return. This is the corpus's Known Gap 14 (3,867 deployed-accepts-only cases in a pinned 73,573-case family, recorded in public log entry 13). It was found by the project's own differential harness; a post-appeal review round added a faithful RFC oracle as a third comparison, which showed the deployed verifier — not the mechanized model — was the one deviating from RFC 9162, and traced it to the missing terminal check. Scope: verify_consistency's only production caller is the consumer-side pin store, reached only behind a verified head signature. Generation is RFC-correct and unaffected; the live provider service does not run this verifier; the published standalone verify.py has no consistency verifier. An empirical search found 0 realizable pin-advance poisons against an honestly pinned consumer, consistent with Known Gap 14's non-claim. Verification: - New fail-first three-way regression test test_consistency_lied_size_three_way_agreement (deployed / recursive ConsRec model / independent faithful RFC 9162 transliteration) over the honest AND lied-size families; fails pre-fix, passes post-fix. - Historical differential tests (164,479 inclusion; 164,224 consistency) unchanged — the fix rejects nothing honest. - Full suite: 145 passed, 0 failed. Public log entry 13, the attested accumulator commit, and the IACR submission PDF are all unchanged. Vulnerable state tagged vulnerable/sn0-consistency-fd2f6ba. See docs/security-2026-07-23-consistency-terminal-check.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
81 lines
4.4 KiB
Markdown
81 lines
4.4 KiB
Markdown
# Security note: RFC 9162 Step-7 terminal check restored in `verify_consistency`
|
|
|
|
**Date:** 2026-07-23
|
|
**Component:** `pacta.transparency.verify_consistency` (consumer-side consistency-proof verifier)
|
|
**Severity:** low (RFC-conformance defect; no exploit found through the honest pin-store flow)
|
|
**Vulnerable commit:** tagged `vulnerable/sn0-consistency-fd2f6ba`
|
|
**Status:** fixed; found by this project's own differential fidelity harness.
|
|
|
|
## What was wrong
|
|
|
|
RFC 9162 §2.1.4.2 Step 7 requires a consistency proof to satisfy three
|
|
conditions: both reconstructed roots must match the supplied roots, **and** the
|
|
new-size navigation counter `sn` must reach `0`. That last condition couples the
|
|
consumed proof length to the claimed tree sizes. The deployed
|
|
`verify_consistency` implemented the RFC bit-navigation loop (including the
|
|
mid-loop `sn == 0` guard) but its final return checked only the two
|
|
reconstructed roots — it omitted the terminal `sn == 0` conjunct.
|
|
|
|
Consequence: when the claimed old size is a power of two, the verifier seeds the
|
|
walk with the old root and uses the sizes only as bit-navigation state, so
|
|
several distinct (false) old-size claims navigate one proof identically. A valid
|
|
proof for one transition therefore verifies under a lied size. Flagship: a valid
|
|
`2 → 3` consistency proof is accepted under the false claim `1 → 3` when paired
|
|
with the true size-2 root.
|
|
|
|
## Scope and reachability (measured, not assumed)
|
|
|
|
- The defect is confined to `verify_consistency`. Its **only** production caller
|
|
is the consumer-side pin store (`sthstore.check_sth_against_store`), which is
|
|
reached only behind a verified head signature in all three of its callers
|
|
(`attestation.py`, `cli.py`, `logclient.py`).
|
|
- Consistency-proof **generation** (`consistency_proof`) is RFC-correct and was
|
|
never affected; every honest proof the system emits verifies everywhere.
|
|
- The **live** LTL provider/mirror service does not run `verify_consistency` in
|
|
its serving path (it generates proofs, it does not verify them). The published
|
|
standalone `verify.py` has no consistency verifier at all (inclusion +
|
|
receipts only). So this is a consumer-library defect, not a live-service one.
|
|
- An empirical search for a pin-advance poison against an **honestly** pinned
|
|
consumer found **0** realizable cases: the two-root check still binds the new
|
|
root, and the pin binds `(size, root)` from the consumer's own store, so the
|
|
lied-size acceptance does not translate into a pin poison for an honest pin.
|
|
No exploit is claimed; the fix restores conformance regardless.
|
|
|
|
## The fix
|
|
|
|
One conjunct in the final return of `verify_consistency`:
|
|
|
|
```python
|
|
return old_hash == old_root_hash and new_hash == new_root_hash and sn == 0
|
|
```
|
|
|
|
## Verification
|
|
|
|
- **Fail-first regression** `test_consistency_lied_size_three_way_agreement`
|
|
(in `tests/test_paper_verifiers.py`): a **three-way** harness comparing the
|
|
deployed verifier, the recursive `ConsRec` model, and an independent faithful
|
|
RFC 9162 §2.1.4.2 transliteration, over both the honest family and the
|
|
lied-size family. It fails against the pre-fix verifier (the flagship lie is
|
|
accepted) and passes once `sn == 0` is restored.
|
|
- The historical differential tests (164,479 inclusion; 164,224 consistency)
|
|
stay green — the fix rejects nothing honest.
|
|
- Full suite: 145 passed, 0 failed.
|
|
- The independent RFC oracle is honest-complete on the tested range but is not
|
|
yet cross-checked against a second independent implementation (ATL / Sigsum);
|
|
that cross-check is a follow-up harness item.
|
|
|
|
## Honesty / provenance
|
|
|
|
This defect is the corpus's Known Gap 14: the project's own fidelity harness
|
|
**found** the divergence (3,867 deployed-accepts-only cases in a pinned
|
|
73,573-case family) and recorded it in public log entry 13. What the two-way
|
|
harness could not do was assign blame — with only the deployed verifier and the
|
|
Lean model disagreeing, and both labelled "RFC 9162", the divergence was filed
|
|
as a scoped gap rather than a conformance bug. A post-appeal review round added
|
|
the missing third oracle (a faithful RFC verifier), which showed the deployed
|
|
side was the deviant one, and traced it to the omitted Step 7.
|
|
|
|
Public log entry 13 and the attested accumulator commit are unchanged: the
|
|
historical experiment remains truthfully recorded and reproducible at the
|
|
tagged pre-fix commit. The IACR submission PDF is unchanged. The corpus
|
|
Known-Gaps ledger records this closure.
|