Skip to content

Verifying Event Signatures

This guide explains how to verify the integrity and authenticity of events returned by EventSourcingDB. You'll learn how signatures work, how to validate them, and what to consider when using them in your application.

Signed Events

When EventSourcingDB is started with the --signing-key-file flag, it automatically signs every event that is returned by the API. This includes events returned when:

The signature is calculated using Ed25519, a modern and efficient digital signature algorithm. The signing key must be a PKCS#8-encoded Ed25519 private key in PEM format.

Here's what a signed event looks like. Pay special attention to the signature field, which includes the prefix esdb:signature:v1:. This prefix indicates the origin and version of the signature format, making it easier to identify and verify signatures in distributed systems – and to distinguish those generated by EventSourcingDB from others:

{
  "specversion": "1.0",
  "id": "0",
  "time": "...",
  "source": "https://library.eventsourcingdb.io",
  "subject": "/books/42",
  "type": "io.eventsourcingdb.library.book-acquired",
  "datacontenttype": "application/json",
  "data": {
    "author": "Arthur C. Clarke",
    "isbn": "978-0756906788",
    "title": "2001 – A Space Odyssey"
  },
  "predecessorhash": "...",
  "hash": "...",
  "signature": "esdb:signature:v1:..."
}

If no signing key is configured, the signature field will still be included, but its value will be null:

"signature": null

Signatures Are Ephemeral

EventSourcingDB does not store signatures permanently. They are calculated on the fly when an event is returned. If the signing key changes, previously returned events will receive different signatures.

You can still validate those events, but only if you fetch them again. Verifying a cached signature is only possible if the signing key hasn't changed in the meantime.

Verifying Events

EventSourcingDB does not persist signatures, nor does it provide a built-in endpoint for signature verification. Instead, clients are responsible for verifying signatures locally – using the provided event fields, the public signing key, and standard cryptographic libraries.

Each event returned from EventSourcingDB includes a hash and, if signing is enabled, a signature. To verify the authenticity of an event:

  1. Recalculate the hash

    The hash field tells which version of the hash it is. A hash that starts with esdb:hash:v2: is of version 2; a hash without such a prefix is of version 1. An event keeps the version it was written with: EventSourcingDB writes version 2 since version 1.3, and events written before keep their hash of version 1. To verify the events of any store, support both.

    For version 2, write the following values one after another, each on a line of its own, separated by a single line feed (\n), and without a line feed after the last one:

    1. the prefix esdb:hash:v2:
    2. specversion
    3. id
    4. predecessorhash
    5. time
    6. source
    7. subject
    8. type
    9. datacontenttype
    10. traceparent
    11. tracestate
    12. data

    Write a field that the event does not have, such as a missing traceparent, as a single -. Take data exactly as it appears in the response, byte for byte: do not parse data and serialize it again, since a JSON library may order keys, escape characters, or format numbers differently, and the hash would no longer match. Then compute the SHA-256 hash of the result, write it as lowercase hexadecimal, and put esdb:hash:v2: in front of it. The result must equal the hash field.

    For version 1, hashing is performed in three steps:

    • First, concatenate the following metadata fields using a pipe (|) as a separator – without any spaces before or after the separator:

      specversion | id | predecessorhash | timestamp | source | subject | type | datacontenttype

      Then compute the SHA-256 hash of this string – this is the metadata hash.

    • Next, compute the SHA-256 hash of the event's data field exactly as it appears in the response, byte for byte – this is the data hash. Do not parse data and serialize it again: a JSON library may order keys, escape characters, or format numbers differently, and the hash would no longer match.

    • Finally, concatenate both metadata and data hashes (as hexadecimal strings) and compute another SHA-256 hash over the result. This is the final event hash.

    Version 1 does not cover traceparent and tracestate.

    A hash whose prefix names a version your client does not know cannot be verified by it. Treat it as unsupported, not as tampered with.

  2. Extract the raw signature

    The signature field, if present, starts with the prefix esdb:signature:v1:. This prefix is not part of the actual signature and must be removed before verification. The versioning allows future changes to the format while remaining backward-compatible.

    For signature:v1, the remaining part is a hex-encoded Ed25519 signature of the value of the event's hash field, including its prefix, if it has one.

    This means the signature is computed using the Ed25519 algorithm over the event hash as text – the characters of the hash field, its prefix included, not the 32 bytes that its hexadecimal digits encode – and then encoded as a lowercase hexadecimal string. The result is appended to the prefix esdb:signature:v1: to form the complete signature field value.

  3. Validate the signature

    Use the public key corresponding to the private key configured in the EventSourcingDB instance to verify the signature against the bytes of the hash field, taken as text. The public key must be obtained through a secure, out-of-band mechanism.

If your application requires signatures, treat unsigned events or signature mismatches as errors. If you only need to ensure data integrity, verifying the hash alone is sufficient.

Hash vs. Signature

The hash ensures that the event's content is intact. The signature proves that it originated from a trusted, signing-enabled EventSourcingDB instance.

Hash vs. Signature

EventSourcingDB uses hashes to ensure that the event data has not been modified. Every event contains a hash field, which is computed from the full content of the event – in version 1 of the hash, from all of it except traceparent and tracestate.

This mechanism guarantees data integrity, even if no signing key is configured.

If a signing key is configured, EventSourcingDB also provides a signature field. This enables authenticity checks, ensuring that the event was not only unmodified but also issued by a trusted instance of EventSourcingDB.

You can verify the integrity of an event using just the hash. Verifying authenticity requires the signature as well.

Hash Always Available, Signature Optional

Every event has a hash, even if the database was not started with a signing key. The signature field, on the other hand, is only non-null if signing is enabled.

Key Rotation and Its Implications

Because EventSourcingDB does not store signatures, they are always generated on the fly using the currently active signing key.

This has important consequences:

  • The same event may receive different signatures at different points in time, if the signing key has changed.
  • Previously fetched signatures become unverifiable after a key rotation.
  • To verify an event, you must re-fetch it from the database, so it gets signed with the current key.

Avoid Long-Term Caching of Signatures

Never store a signature with the expectation that it will remain valid forever. Signatures are valid only with respect to the current key. If the key changes, so do the signatures.

However, even if the signing key is rotated, hash verification continues to work as expected – because the hash is based on the event content and does not depend on any external key.

Generating a Signing Key

You can generate a signing key using OpenSSL. The key must be Ed25519 and encoded in PKCS#8 format:

openssl genpkey -algorithm ED25519 -out my-key.pem

The resulting file can be passed to EventSourcingDB using the --signing-key-file CLI flag.

Private Key Only

EventSourcingDB only needs the private key in PKCS#8 PEM format. The corresponding public key can be extracted separately, if needed, using standard OpenSSL tools.

When to Use Signature Verification

Event signature verification is particularly useful in scenarios where:

  • Events are exchanged across system boundaries.
  • You want to ensure end-to-end authenticity, e.g., for compliance or auditing purposes.
  • You're building systems with untrusted clients or require cryptographic proof of origin.

For example, if events are transmitted from a service in one data center to a processing pipeline in another, signatures help prove that the data has not been tampered with in transit.

If you simply want to check for accidental corruption or ensure consistency between services, hash validation is usually sufficient.

Best Practices

To make signature verification reliable and secure in production systems, consider the following best practices:

  • Always verify the hash before checking the signature.
  • Never store or transmit signatures as permanent values.
  • Treat missing or invalid signatures as errors only if your application explicitly requires authenticity.
  • Distribute public keys securely and track key rotation procedures carefully.
  • Use built-in verification helpers provided by the official EventSourcingDB client SDKs whenever possible.