Skip to content
New kind of attack — caught and fixed before it was publicly known·Read the timeline
Probity Open Predicate Standard
/predicate/v1/quotation-fidelity
View raw .md →

quotation-fidelity v0.1

A signed assertion that a quoted span appears verbatim in a committed source, checkable against a 32-byte root by a reader who holds none of that source.

Predicate type: https://getprobity.dev/predicate/v1/quotation-fidelity

Status: draft. The namespace https://in-toto.io/attestation/quotation/v0.1 is unclaimed and is the intended destination; this document is written to be portable to it.

What it asserts

That a quoted span appears verbatim at a stated position in a committed source, checkable against a 32-byte root by a reader holding none of that source, and how the source was obtained.

Why this exists

Three quotations were fabricated in three documents in one day, and every one was caught by a reader rather than by anything we own. The shared mistake was quoting from a summary of a source rather than from the source: faithful enough to feel quotable, lossy enough that the words are no longer the author's, and nothing about the sentence looks wrong on the page.

A gate now catches that locally. It cannot travel. A reader who wants to check a quotation needs the capture corpus, which is 5.9 GB, gitignored and mirrored off-repository — so the check is available to us and to nobody else. This predicate is the travelling form.

What was already built, and what was missing

Two vocabularies exist and neither has teeth, which is exactly the gap:

  • W3C PROV defined prov:Quotation and prov:wasQuotedFrom in 2013 — "some taken Entity from an earlier, larger Entity" — with no digest, no signature and no location.
  • W3C Web Annotation defines TextQuoteSelector (exact/prefix/suffix) and TextPositionSelector (start/end) — the location, with no cryptographic binding.

PROV supplies the relation. Web Annotation supplies the location. The binding that would make either checkable is what nobody built. This predicate is only that binding; the vocabulary is reused rather than reinvented.

C2PA already solved the hard part and aimed it at pixels: c2pa.hash.bmff does Merkle-over-chunks with an init hash and a hash array, so a verifier can check one chunk without holding the file. For text its specification mandates a whole-asset hash and states that partial verification is "not supported by design." The machinery transfers to canonical text segments at the cost of a translation, not a new cryptographic idea.

Shape

1. The source is committed as a Merkle tree over canonical text segments

Not as a digest of the whole file. A whole-file digest forces a verifier to hold the whole file, which is the thing that makes the local gate untravellable.

The tree is RFC 9162 (the Certificate Transparency tree), chosen because it is the only candidate that is short-proof, short-state and already registered: RFC 9942 registers it as RFC9162_SHA256 with a COSE-carryable inclusion proof, so the commitment is natively expressible in SCITT (RFC 9943) without inventing a structure.

At ~1 KB segments over a 5.9 GB corpus (n ≈ 6×10⁶) an inclusion proof is ~23 sibling hashes ≈ 736 bytes, verifier state is 32 bytes, and the check is ~23 SHA-256 compressions.

Alternatives were considered and lose structurally, not incidentally. Redactable and sanitisable signatures prove membership but not position, and have no COSE algorithm identifier. KZG and vector commitments give 48-byte proofs but need a trusted setup and pairings to save 700 bytes nobody needs. SD-JWT is flat, so a verifier would hold six million digests. BBS+ buys unlinkability, which a public citation corpus actively does not want.

2. Canonical segmentation is normative, and it is the more durable claim

Two implementations that segment differently produce different roots for the same bytes, so the segmentation profile is part of the predicate rather than an implementation detail. Whoever writes this profile defines what "verbatim" means downstream — a more durable contribution than the predicate itself.

The normalisation is the one this project's gate already learned the hard way, and each rule cost a real defect: NFKC; typographic quotes, dashes and non-breaking spaces folded; soft hyphens removed; a hyphen before a line break preserved (removing it broke a verbatim quotation of a compound word that happened to wrap); \n and \t inside JSON string captures unescaped (a document quoted inside a JSON capture otherwise fails at 96%); leading blockquote and list markers stripped per line, because specifications put their normative callouts in blockquotes and that is precisely the text people quote; whitespace collapsed; lowercased.

An offset map back to raw bytes is required, not optional. Without it an implementation can locate a quotation and still not say where it lives in the artifact being attested to.

3. Acquisition is required, and a predicate that cannot state it is not signable

This is the field that keeps the artifact honest, and it exists because the first design of this predicate was circular.

Committing an excerpt beside a digest and having a verifier recompute one from the other proves only that the author was self-consistent — no step contacts the source. Signing that is worse than leaving it unsigned, because a signature over a self-reported capture converts an unverified assertion into an attested one. For a project whose thesis is evidence a distrusting party can re-check, that is the failure we exist to prevent, in the artifact we sell.

Requiring the strongest tier instead would make the predicate useless for the sources that matter most. A competitor's page has no third-party custody and will attest nothing; a chat thread has neither. Refusing to emit evidence about those is refusing to do the work. Emit it and say what it is — the same rule this predicate applies to UNCHECKED, and that the sibling execution predicate applies to a self-reported clean row.

(The internal design note behind this field is docs/architecture/ACQUISITION-PROVENANCE.md in the Probity repository. It is named rather than linked: this document is published standalone, and a relative link out of the specification tree resolves to nothing for a reader who has only the specification — which is the exact defect that once left 139 cross-references broken on the published site.)

acquisitionMeansA challenger can
self-capturedWe fetched it and stored itRe-fetch and compare, if the source still exists unchanged
self-captured-witnessedPlus a digest in an append-only log at capture timeProve we did not alter the capture afterwards, and place it in time
third-party-custodyAn independent archive holds a copyFetch the bytes from someone who is not us
origin-attestedCryptographic proof the origin served these bytesVerify without trusting us or the archive

The weakest value in a bundle is surfaced in every rendering of it. A bundle is exactly as strong as its weakest acquisition, and a reader who has to compute that will not. self-captured is never upgraded by re-signing; only by obtaining different evidence.

Verdicts

Three, and the middle one is the point:

VERIFIEDThe span appears verbatim in the committed source at the stated position
ALTEREDThe source is committed and the wording differs. A finding.
UNCHECKEDNo commitment covers this span. Not a finding, and must never be reported as one.

An unchecked quotation is exactly as unverified as a fabricated one from this predicate's point of view, and saying so is the honest output. A verifier that reports UNCHECKED as a pass has reproduced the defect this exists to refuse.

An elision is not an alteration. A span written with an ellipsis asserts that its parts appear in that order with text omitted between them, which is honest quoting; each segment is verified separately and in order, and segments too short to be distinctive are rejected so an elision cannot smuggle in a match on stopwords.

Carriage

DSSE, over application/vnd.in-toto+json.

Not a Rekor intoto entry. Rekor v2 GA dropped that entry type; only hashedrekord and dsse remain, so any design assuming an intoto entry is already broken against current infrastructure.

What this does not assert

  • That the source is true, or that its author meant what the quotation implies. It asserts transcription fidelity and nothing about meaning.
  • That the excerpt is authentic at acquisition: self-captured. At that tier anyone with commit rights can commit a false excerpt and a matching root. That is why the field is required and why its weakest value is surfaced — the limit is made legible rather than hidden.
  • That the source still says this today. The commitment is to bytes captured at a stated time. Sources drift: better than 75% of scholarly references were measured to have drifted in one study, so a pin against a live source is a review scheduler, not a security control.
Get access

Run your agents through Probity.

A few details about your setup and we'll get you gating agents fast.