Transparenzprotokoll
GET https://api.kennmarq.com/v1/log/root
GET https://api.kennmarq.com/v1/log/proof/{entry}
GET https://api.kennmarq.com/v1/log/consistency
GET https://api.kennmarq.com/v1/log/entries
GET https://api.kennmarq.com/v1/log/roots
GET https://api.kennmarq.com/v1/log/key
Jede Signatur und jeder Zeitstempel, den wir ausgeben, wird zuerst an ein öffentliches, nur erweiterbares Protokoll angehängt. Eine Wurzel über das gesamte Protokoll wird jede Stunde signiert, solange es wächst, und mindestens einmal am Tag. Mit einer Wurzel, ihrer Signatur und unserem öffentlichen Schlüssel kann jeder prüfen, dass ein Eintrag im Protokoll steht und dass das Protokoll nie umgeschrieben wurde. Kein Schlüssel nötig. Die Seite des Transparenzprotokolls zeigt die neueste Wurzel.
Was ein Eintrag enthält
Jeder Eintrag ist ein JSON-Objekt mit v (1), kind und logged_at (UTC, auf die Sekunde genau) sowie den Feldern seiner Art. Es sind ausschließlich Hashes, Zeitpunkte und unsere eigenen Kennungen: nie der Name oder die ID einer Organisation, ein Dateiname, eine Wasserzeichen-ID, ein Modell- oder App-Name. Wer eine Datei oder ein Token hat, kann die Felder seines Eintrags nachrechnen; alle anderen erfahren nichts daraus.
| kind | Erfasst | Felder |
|---|---|---|
c2pa.signature | Eine über POST /v1/mark markierte Datei | environment, manifest (sein Label), asset_sha256 (die Datei, wie wir sie zurückgegeben haben), claim_signature_sha256 (die COSE-Signatur: das CBOR in der Box c2pa.signature des Manifests), signer_sha256 (das Signaturzertifikat, DER), tsa_serial, tsa_time, tsa_token_sha256 (das Zeitstempel-Token in der Signatur, DER) |
c2pa.local_signature | Ein über POST /v1/sign signiertes Manifest (lokaler Modus) | environment, manifest, binding_sha256 (der Hash in der Assertion c2pa.hash.data des Manifests: Die Datei sehen wir nie), claim_signature_sha256, signer_sha256, tsa_serial, tsa_time, tsa_token_sha256 |
rfc3161.time_stamp | Ein Token von POST /v1/tsa/{key} | environment, tsa_serial, tsa_time, tsa_token_sha256 (das TimeStampToken, DER), imprint_alg und imprint (der Hash, den Sie gesendet haben) |
audit.head | Der jüngste Stand des Audit-Logs einer Organisation, verankert, sobald er sich ändert | org (SHA-256 von "audit-org:", gefolgt von der ID der Organisation), seq und head (letzter Eintrag und Hash der Audit-Kette) |
checker.result | Ein signiertes Ergebnis von POST /v1/verify | jws_sha256 (der kompakte JWS, wie zurückgegeben), file_sha256 (die geprüfte Datei), key_id (der Schlüssel des Prüftools: SHA-256 seiner DER-codierten SubjectPublicKeyInfo) |
Das Protokoll ist ein Merkle-Baum nach RFC 9162 (das Hashing von Certificate Transparency): Der Leaf-Hash eines Eintrags ist der SHA-256 eines 0x00-Bytes, gefolgt vom kanonischen JSON des Eintrags (Schlüssel sortiert, keine Leerzeichen; jeder Wert ist ASCII). API-Antworten enthalten genau diesen Text als entry_json. Die Antworten auf Ihre Aufrufe von POST /v1/mark und POST /v1/tsa nennen den erzeugten Eintrag in X-Log-Index.
Die neueste Wurzel
curl https://api.kennmarq.com/v1/log/root{
"object": "log_root",
"seq": 412,
"tree_size": 1834467,
"timestamp": 1794067200000,
"signed_at": "2026-11-12T00:00:00.000Z",
"root_hash": "9c8d5e0a4b7f…",
"key_id": "5b2e7a19c0d4…",
"signature": "MEUCIQDx…"
}signature ist ECDSA P-256 mit SHA-256 (DER, Base64) über den Tree Head nach RFC 6962: die Bytes 00 01, dann timestamp (Millisekunden) und tree_size als 8-Byte-Zahlen in Big-Endian, dann die 32 Bytes von root_hash. GET /v1/log/roots listet jede Wurzel auf, die neueste zuerst (?before=<seq> blättert zurück, ?limit= bis 100).
Inklusionsbeweis
Geben Sie den Eintrag über seinen Index (X-Log-Index), seinen Leaf-Hash oder, bei einer markierten Datei, das Label ihres Manifests an (urn:c2pa:…, mit jedem C2PA-Tool lesbar). Fügen Sie ?tree_size= hinzu, um ihn gegen eine bestimmte signierte Wurzel zu beweisen; sonst wird die neueste verwendet.
curl https://api.kennmarq.com/v1/log/proof/urn:c2pa:3fa85f64-5717-4562-b3fc-2c963f66afa6{
"object": "log_proof",
"status": "included",
"leaf_index": 1834001,
"leaf_hash": "d1f0…",
"entry": { "v": 1, "kind": "c2pa.signature", "manifest": "urn:c2pa:3fa85f64-…", "asset_sha256": "…", … },
"entry_json": "{\"asset_sha256\":\"…\",…,\"v\":1}",
"tree_size": 1834467,
"audit_path": ["7a3c…", "0e91…", …],
"root": { "object": "log_root", "seq": 412, … }
}Für einen Eintrag, der neuer ist als die neueste Wurzel, lautet die Antwort "status": "pending", mit dem Eintrag und noch ohne Beweis: Die nächste Wurzel erfasst ihn innerhalb einer Stunde.
Konsistenzbeweis
Der Beweis, dass der Baum aus second Einträgen mit dem Baum aus first beginnt: Dazwischen wurde nichts entfernt, verändert oder umsortiert. Beide müssen Größen signierter Wurzeln sein; second ist standardmäßig die neueste. Bewahren Sie die Wurzeln auf, die Sie gesehen haben, und prüfen Sie jede neue gegen sie.
curl "https://api.kennmarq.com/v1/log/consistency?first=1790012&second=1834467"{
"object": "log_consistency",
"first": { "object": "log_root", "tree_size": 1790012, … },
"second": { "object": "log_root", "tree_size": 1834467, … },
"proof": ["44be…", "c07d…", …]
}Alle Einträge
Für Prüfer, die den Baum selbst nachbauen: ?start= und optional ?end= (exklusiv), bis zu 1.000 Einträge je Aufruf, die ältesten zuerst, jeweils mit leaf_index, leaf_hash, entry und entry_json.
curl "https://api.kennmarq.com/v1/log/entries?start=0&end=1000"Der öffentliche Schlüssel
curl "https://api.kennmarq.com/v1/log/key?format=pem" -o kennmarq.com-log-key.pemOhne format=pem antwortet der Endpunkt mit JSON: key_id (SHA-256 der DER-codierten SubjectPublicKeyInfo des Schlüssels, wie jede Wurzel ihn nennt), algorithm und public_key. Ein älterer Schlüssel bleibt über ?key_id= abrufbar.
Selbst prüfen
Die Signatur der Wurzel, mit OpenSSL (und jq):
curl -s "https://api.kennmarq.com/v1/log/key?format=pem" -o log-key.pem
curl -s "https://api.kennmarq.com/v1/log/root" -o root.json
printf '0001%016x%016x%s' "$(jq .timestamp root.json)" "$(jq .tree_size root.json)" \
"$(jq -r .root_hash root.json)" | xxd -r -p > tree-head.bin
jq -r .signature root.json | base64 --decode > signature.der
openssl dgst -sha256 -verify log-key.pem -signature signature.der tree-head.bin # Verified OKEin Inklusionsbeweis (RFC 9162 § 2.1.3.2), in Python:
import hashlib, json, urllib.request
proof = json.load(urllib.request.urlopen("https://api.kennmarq.com/v1/log/proof/urn:c2pa:3fa85f64-5717-4562-b3fc-2c963f66afa6"))
sha256 = lambda data: hashlib.sha256(data).digest()
r = sha256(b"\x00" + proof["entry_json"].encode())
assert r.hex() == proof["leaf_hash"]
fn, sn = proof["leaf_index"], proof["tree_size"] - 1
for step in map(bytes.fromhex, proof["audit_path"]):
assert sn > 0
if fn % 2 == 1 or fn == sn:
r = sha256(b"\x01" + step + r)
while fn % 2 == 0 and fn != 0:
fn, sn = fn >> 1, sn >> 1
else:
r = sha256(b"\x01" + r + step)
fn, sn = fn >> 1, sn >> 1
assert sn == 0 and r.hex() == proof["root"]["root_hash"]
print("in the log, under root", proof["root"]["seq"])Prüfen Sie dann die Signatur dieser Wurzel wie oben und ob die Felder des Eintrags zu Ihrer Datei passen: asset_sha256 ist der SHA-256 der Datei, die wir zurückgegeben haben, und manifest das Label ihres Manifests. Bei einem Zeitstempel ist tsa_token_sha256 der SHA-256 des Tokens in Ihrer Antwort.
Limits und Caching
120 Aufrufe pro Minute je Adresse, mit den üblichen Headern X-RateLimit-*. Antworten zur neuesten Wurzel dürfen eine Minute lang zwischengespeichert werden; Antworten zu einer von Ihnen genannten Baumgröße ändern sich nie und dürfen einen Tag lang zwischengespeichert werden.
Fehler
| Status | Code | Wann |
|---|---|---|
| 400 | invalid_request | Ein Parameter ist keine ganze Zahl, eine Baumgröße gehört zu keiner unserer signierten Wurzeln, oder ein Eintragsname ist fehlerhaft. |
| 404 | not_found | Kein solcher Eintrag oder Schlüssel, oder noch keine Wurzel signiert. |
| 429 | rate_limited | Mehr als 120 Aufrufe pro Minute von einer Adresse. Retry-After gibt an, wann Sie es erneut versuchen können. |
| 500 | internal_error | Auf unserer Seite ist etwas fehlgeschlagen, auch ein Beweis, der sich vor dem Versand nicht verifizieren ließ. Versuchen Sie es erneut. |