Skip to content
Fohrkast

Proof format

hash_v1: the audit-trail hash format

Every event in a Fohrkast audit trail carries two SHA-256 hashes in this format. The format is frozen: rows already hashed never change format, and any successor would be named hash_v2 and apply to new rows only. The checker shipped in every evidence pack, verify.mjs, recomputes both hashes offline.

Byte layout

Each input is a UTF-8 string of the fields below joined with a single | (0x7C). An empty value contributes zero bytes. Output is lowercase hexadecimal.

payload_sha256 = SHA-256( actor | subject_id | thread | body_text | sealed_hex | retention_class )
hash           = SHA-256( prev_hash | tenant_id | seq | type | at | recorded_at | payload_sha256 )

A chain is intact when, for each event in sequence order, seq is the previous seq plus 1, prev_hash equals the previous event's hash, and both hashes recompute; the stored chain head equals the last hash. An event emptied at the end of its retention period keeps seq, prev_hash and hash only, and is checked as a link.

Fields

prev_hash
The previous event's hash; for the first event, 64 characters 0.
tenant_id
The organisation's UUID, lowercase, with hyphens.
seq
The event's sequence number in decimal, from 1, without gaps.
type
The event type, for example case.opened.
at, recorded_at
UTC, YYYY-MM-DDTHH:MM:SS.ffffffZ, always six fractional digits.
actor
The actor identifier, or empty.
subject_id, thread
UUID, lowercase, with hyphens, or empty.
body_text
The event body as PostgreSQL prints jsonb: keys ordered by byte length, then bytewise; ": " after each key; ", " between members. Exported verbatim in every evidence pack.
sealed_hex
The sealed note's ciphertext bytes in lowercase hex, or empty.
retention_class
consent_proof, case_record, processing_log or tenant_life.

Example

Payload input bytes:

u-example||7c9e6679-7425-40de-944b-e07fc1f90ae7|{"channel": "web", "case_type": "incident"}||case_record

payload_sha256 = cdd03a9454f5617e3d59cbae6caccf48a44e5a86881313423253788bb438fae8

Hash input bytes:

0000000000000000000000000000000000000000000000000000000000000000|3f1c2a9e-5b7d-4e2a-9c1b-0d8e7f6a5b4c|1|case.opened|2026-10-19T08:30:00.000000Z|2026-10-19T08:30:00.123456Z|cdd03a9454f5617e3d59cbae6caccf48a44e5a86881313423253788bb438fae8

hash = 5a494e6d3975ad8227ccc0c921ab7c32f85a8b48e00e85ca3fe403c1d95cc04d