robertolocatelli81-dev/cryptovalid-opencore: cryptovalid-opencore 0.14.0 — checkpoints, witnesses, split-view evidence, SCITT
Abstract
0.14.0 — checkpoints, witness cosignatures, split-view and freshness (2026-09-19) Two limits declared in the README since 2026-08-17 — split-view / equivocation and rollback / freshness were "future work" — are now measured features, built with the transparency ecosystem's own formats so that the ecosystem's own software verifies the result. cryptovalid_checkpoint.py — a ledger checkpoint (origin, tree size, RFC 6962 root of the canonical entries) signed as a C2SP signed note by the log key (c2sp.org/tlog-checkpoint, c2sp.org/signed-note); vkey strings; strict where the spec is strict (tree size, canonical base64 root, control characters) with two declared caps (100 signature lines — the Go library's ceiling, the spec floor being 16 — and 8 KiB per line), permissive where the spec says so (any non-empty origin line: production origins such as go.sum database tree parse). Every line of a known key is verified — the spec's MUST; the Go library verifies the first line per key and drops repeats, so a note with a bogus repeat opens there and is refused here (declared, fail-closed). Two trusted keys colliding on one 4-byte key id are refused as ambiguous (tested with a real collision found by brute force). Log keys of types Ed25519 (0x01) and ECDSA P-256/384/521 (0x02, cryptography required) are verified; cosignatures Ed25519 (0x04) and ML-DSA-44 (0x06); keys of types nobody verifies here (RFC 6962 THS 0x05, unassigned) are ignored, never accepted. cryptovalid_witness.py — a witness (c2sp.org/tlog-witness): keeps the last checkpoint it cosigned per origin (trust on first use, stated), cosigns only when the log signature verifies and the new tree extends the stored one (consistency proof), refuses forks, rollbacks and unproven extensions. Every refusal that involves two log-signed checkpoints is returned to the caller; the pairs that are proof (split-view: two log-signed roots at one size, met at the stored size, at a size the witness cosigned earlier, or at one it refused earlier without proof and is now passing) are appended once each to .evidence.jsonl, with lines from unknown keys stripped; rollback and unproven-extension are not proof: they are counted (last 100 per origin as size/root/time) and the notes refused without proof beyond the stored size are kept in a pending ring of at most 100 per origin, so replaying a log's public history cannot grow the evidence file and grows the state only up to those rings. Cosignatures are cosignature/v1 (c2sp.org/tlog-cosignature, timestamp inside the signed bytes; zero and values above 2^63−1 refused). serve exposes the add-checkpoint endpoint (200 with the signature lines, 400, 403, 404, 409 with the size as text/x.tlog.size, 422 — in the spec's order; 500 only with a reason on stderr, never a dropped connection; check-and-persist under a process lock and a POSIX file lock); submit is the log side (old size 0, then 409 → the witness's size and our proof); several trusted keys per origin (rotation) and origins that differ from the key name are configured explicitly. verify_witnessed is the relying party: log signature, expected origin, ≥ N distinct trusted witnesses on this exact text and, with max_age_s, ≥ N of them younger than it (newest cosignature per witness; a witness in the future beyond clock_skew_s does not count). Log keys and cosignature types the ecosystem uses (from the competitor/market review of 2026-09-19 with Gemini 3.1 Pro on primary sources): ECDSA log keys (0x02, Rekor v1) are verified — measured on the three real rekor.sigstore.dev checkpoints downloaded that day (2.77 billion entries on the active shard), by our verifier and by the Go oracle, refused when tampered, and cosigned by our witness; ML-DSA-44 cosignatures (0x06, the type the witness spec now recommends) are emitted with serve --pq-key beside the Ed25519 line and verified by transparency-dev/formats (filippo.io/mldsa). cryptovalid_scitt.py — SCITT (RFC 9943): Signed Statement (COSE_Sign1 with CWT Claims iss/sub, content type, kid; attached or detached payload), registration on the ledger (the entry binds the statement's SHA-256), Receipt with the CWT Claims RFC 9943 requires plus the RFC 9942 inclusion proof, Transparent Statement (receipts under label 394); offline, fail-closed relying-party verification. Measured with pycose, an independent COSE implementation (both signatures, labels 15/394/395, wrong key and tampered payload refused); pycose is required in CI. Stated: EdDSA only, the RFC9162_SHA256 profile (not CCF's), no public Transparency Service. cryptovalid_monitor.py --match REGEX — identity/subject search over the canonical entries during a monitor run, with the new matches since the last green state (what rekor-monitor does for identities), so any match can be proven with an inclusion receipt. verifiers/note_oracle/ — an independent Go verifier with no cryptovalid code (golang.org/x/mod/sumdb/note + github.com/transparency-dev/formats/note at the commit the reference witness uses), built and required in CI: every listed key must have signed; a tampered byte or a missing cosigner is exit 1. Measured against the reference witness (github.com/transparency-dev/witness @ 55a5a0bf332a, 2026-09-18, the code the Armored Witness runs), three ways: its in-process Update cosigned our size-5 checkpoint, refused 5→8 without a proof, cosigned it with our RFC 6962 proof, refused a fork at 8 ("roots do not match") and rollbacks; its HTTP client drove our serve with the same outcome at every step; our submit drove its HTTP server (200, 409 then proof, 422 on the fork, 409 on rollbacks) and its cosignature verified in verify_witnessed and in the Go oracle (VERIFIED sigs=3 unverified=0 on a note cosigned by both witnesses). Review: four independent reviewers — Gemini 3.1 Pro, Claude Opus 5, Sonnet 5, Haiku 4.5 — in rounds 1, 2, 4, 12 and 13; round 13 was the last: its findings (an explicit cosignature timestamp checked for monotonicity only after side effects, a Go-oracle test class without the no-cryptography guard, stale sentences on key types) were re-measured and fixed, and the tests were re-run, without a further review round; rounds 3 and 5 to 11 ran with the three Claude models only (Gemini's API credits were exhausted in those hours) — the dossier lists every round with every finding. Gemini 3.1 Pro also produced the competitor/market gap review on primary sources that led to the ECDSA, ML-DSA-44, SCITT and identity-search additions. Every finding was re-measured on the code before being accepted; among the real ones fixed: the first vkey parser split on every + (base64 can contain one), production origins with spaces were refused, non-proof refusals were labelled proof, freshness counted the newest witness instead of N fresh ones, a re-cosign over HTTP answered an empty body, the evidence file and the state could be grown by replaying a log's public checkpoints, a real Rekor v1 (ECDSA) vkey raised instead of being ignored, non-ASCII where base64 was expected raised instead of refusing, a re-cosigned note kept its stale line first. Tests: test_cryptovalid_witness.py (27 tests: spec vector of signed-note, key-id collision, tampering, key substitution and role separation, fork/rollback/unproven-extension evidence, quorum and freshness, tlog-witness status codes, hostile HTTP input, cross-process lock, real Rekor checkpoints, ML-DSA-44, Go oracle with positive controls) and test_cryptovalid_scitt.py (5 tests, pycose oracle); CI runs it with the Go oracle required and, on Python 3.9 and 3.11, in a clean venv without cryptography (parsing and NON_VERIFICATA paths). Stated limits: log keys Ed25519 or ECDSA, cosignatures Ed25519 or ML-DSA-44, RFC 6962 THS keys ignored, and our own logs sign Ed25519 only; the monitoring (GET) side of tlog-witness is not implemented; no public witness has cosigned an OMEGA ledger (there is no public OMEGA log — the interop is local, with the reference implementation); a log that feeds disjoint subsets of the trusted witnesses different views gets both cosigned whenever a relying party's quorum fits inside one subset (why witness policies fix the set and demand N > M/2); a second root at a size this witness never cosigned and no longer remembers (never seen, or dropped from its bounded rings) is a rollback it cannot turn into proof; the witness state and evidence files' integrity is the operator's; pre-anchor window and selective omission are unchanged. Deposit: the software is archived on Zenodo — concept DOI 10.5281/zenodo.22539578 (always the latest version); the version DOI of 0.14.0 is added here once Zenodo has archived this release.