Tidenda

Signatures and trust

The three kinds of signature, what each proves, the public trust list and key rotation.

On this page

Tidenda signs what it sends and what it publishes, so that receivers and anyone downstream of them can check where content came from and that it has not changed. Integrators and security reviewers use this overview to decide which signatures to check and what each one proves.

Signature Algorithm Covers Proves
Webhook request HMAC-SHA256 A timestamp and the raw request body This request came from the platform, unaltered and recent
Document snapshot Ed25519 The snapshot JSON of a published version This document version is exactly what the platform published
Media and transcripts Ed25519 Original image or audio bytes, or one transcript segment This file, or this transcribed passage, is what the platform holds

Webhook request signatures

Every webhook delivery carries an HMAC-SHA256 signature in the X-Tidenda-Signature header, keyed with a secret shared between the platform and one receiver. It covers a timestamp and the exact body bytes, so a valid signature means the request is genuine, unmodified and fresh, and a receiver that rejects old timestamps cannot be fed a captured request again.

The proof ends at the receiver. Nobody else holds the secret, so the signature means nothing once the content moves on. Every receiver checks it; the recipe is in Implementing a webhook receiver.

Content signatures

When a document reaches usable or withheld, the platform stores an immutable snapshot of that version and signs its bytes with Ed25519. Images and audio files are signed the same way when they are uploaded, over their original bytes. An editor who checks a transcript passage against the recording and marks it verified creates a signature over that one segment: the recording's id, the segment's id, its start and end times, its speaker and its text.

These signatures are public-key signatures. Anyone holding the public key can check them, so a signed snapshot can pass through a distribution hub, an archive or a CDN and still be verified by the last system in the chain, without trusting any system in between and without access to a shared secret.

What each signature does not prove:

  • A snapshot signature covers one version. Later edits produce a new version with its own snapshot.
  • An image or audio signature covers the original upload only, not resized or re-encoded copies.
  • A segment signature covers the text as it was when verified. Each verified segment also stores a hash of that text, so a later edit shows as a mismatch.
  • A signature can be absent when content was stored while signing was unavailable. Unsigned content cannot be verified and is unsuitable for redistribution that promises provenance.

Domain separation

All content signatures use the same key. To stop a signature made for one kind of content from verifying as another, each kind prefixes its bytes with its own label before signing:

Kind Prefix
Document snapshot tidenda-snapshot-v1\0
Image tidenda-image-v1\0
Audio file tidenda-audio-v1\0
Transcript segment tidenda-audio-segment-v1\0
Rendered video tidenda-video-v1\0

\0 is a single zero byte. The version in each label would change only if the signed format changed.

The public trust list

The platform publishes the public keys it signs with at GET https://<public-api-host>/api/keys. The endpoint needs no credentials:

{
  "active": "void-2026-05",
  "keys": [
    { "keyId": "void-2026-05", "algorithm": "ed25519",
      "publicKey": "<base64 SPKI DER>", "validFrom": "2026-05-12T00:00:00.000Z" },
    { "keyId": "void-2025-11", "algorithm": "ed25519",
      "publicKey": "<base64 SPKI DER>", "validFrom": "2025-11-01T00:00:00.000Z",
      "validUntil": "2026-05-12T00:00:00.000Z" }
  ]
}

Every signature names the key that made it in its keyId. active is the key used for new signatures, or null when the deployment is not signing. Keys are Ed25519 public keys in base64-encoded SPKI DER, which standard cryptography libraries load directly.

Key rotation

Rotation adds a key: new signatures carry the new keyId, and the previous key stays in the list with a validUntil date, so everything it signed keeps verifying. A key can eventually be removed from the list, after which content it signed can no longer be verified against the platform.

A validUntil in the past means the key no longer signs new content; it does not invalidate what the key signed before. Verifiers cache the list for about an hour and fetch it again as soon as they meet an unknown keyId, which makes a rotation invisible to them.