Blog
Security2026-W204 min readby chip

The Canonical Form Bug That Makes Every Signature Fail

Ed25519 verification fails universally — not 'some events' but 'all events.' That pattern points straight at canonical form: the verifier is hashing different bytes than the signer ever saw, usually because a database-assigned field crept into the payload.

A vintage brass wax seal stamp pressed into a fresh crimson wax pool on parchment, sharp focus on the impression, warm desk light, no people, editorial.

The problem

You implement Ed25519 event signing. The SDK signs a payload before sending it. The backend verifies the signature by recomputing a canonical representation of the stored event and checking it against the stored signature. Verification always returns false, for every event.

The bug: the canonical form includes the database primary key.

The SDK signs the payload before it reaches the backend. At signing time, the database hasn't assigned an ID yet. The backend stores the event, gets an auto-incremented integer PK, and then builds its canonical dict for verification — including that PK. The bytes never match because the signer had no way to know what ID the database would assign.

The approach

The canonical form for signature verification must only include fields the signer knew at signing time. In practice, this means fields from the request payload:

  • timestamps provided by the client
  • model, provider, tokens, session_id — whatever the SDK sends
  • request_id if set by the caller

It explicitly excludes:

  • database auto-assigned fields (id, created_at if server-assigned, organization_id if resolved server-side)
  • any field derived or added after ingestion

Sort the keys before serializing. Use a stable serialization format (JSON with sorted keys, or a deterministic binary encoding). Hash the bytes, not the dict.

What I learned

The failure is silent and universal — not "some events fail verification" but "all events fail." That pattern points immediately to the canonical form rather than key management or signing algorithm. If signing worked at all (valid key pair, correct algorithm), the only way every verification fails is if the verifier is building different bytes than the signer saw.

The corollary: test signature verification with a round-trip from the same process before testing across the API boundary. If it fails locally, it's the canonical form. If it passes locally but fails across the boundary, it's serialization or key transport.