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:
- writing events using
/api/v1/write-events - reading events using
/api/v1/read-events - observing events using
/api/v1/observe-events
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:
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:
-
Recalculate the hash
The
hashfield tells which version of the hash it is. A hash that starts withesdb: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:- the prefix
esdb:hash:v2: specversionidpredecessorhashtimesourcesubjecttypedatacontenttypetraceparenttracestatedata
Write a field that the event does not have, such as a missing
traceparent, as a single-. Takedataexactly as it appears in the response, byte for byte: do not parsedataand 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 putesdb:hash:v2:in front of it. The result must equal thehashfield.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 | datacontenttypeThen compute the SHA-256 hash of this string – this is the metadata hash.
-
Next, compute the SHA-256 hash of the event's
datafield exactly as it appears in the response, byte for byte – this is the data hash. Do not parsedataand 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
traceparentandtracestate.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.
- the prefix
-
Extract the raw signature
The
signaturefield, if present, starts with the prefixesdb: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'shashfield, 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
hashfield, 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 prefixesdb:signature:v1:to form the completesignaturefield value. -
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
hashfield, 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:
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.