The hash chain of a chain: true journal (schema_version 1.2.0).
Every record of a chained journal carries two extra keys:
"prev_hash"-- the"hash"of the record before it; the first record (id 1) links togenesis/0, 64 zeros. One chain spans every segment: the first record of segment N links to the last record of segment N-1."hash"-- lowercase hex SHA-256 of the record's canonical JSON with the"hash"key removed (so it covers"prev_hash", which is what chains it).
Canonical JSON
The canonical form of a decoded JSON value is: objects with keys sorted by
byte order at every depth, arrays in order, no whitespace, strings and
integers as Jason encodes them with escape: :json. It is defined over the
DECODED record, not the bytes a writer happened to emit, so any JSON decoder
can recompute it. Floats are refused at append ({:error, {:float, path}}):
their text form is the one thing in JSON with no single canonical spelling,
and money in a chained journal is a decimal string.
A chained record line on disk is exactly the canonical JSON of the record,
"hash" included. The reader checks that too, so a byte change that decodes
to the same value (a hex-digit case flip inside a \u escape, say) is still
a break.
The chain detects corruption, truncation and naive edits. It carries no key: anyone who can rewrite the journal directory can rewrite the chain.
Summary
Functions
Canonical JSON (see the moduledoc) of a decoded JSON value, as iodata.
Check one decoded record against its predecessor's hash and the raw line it
was decoded from. :ok only when the record links to prev_hash, its
"hash" matches its content, and raw is its canonical encoding.
The prev_hash of the first record of a chain: 64 zeros.
Seal a stamped record onto the chain after prev_hash.
Functions
Canonical JSON (see the moduledoc) of a decoded JSON value, as iodata.
Check one decoded record against its predecessor's hash and the raw line it
was decoded from. :ok only when the record links to prev_hash, its
"hash" matches its content, and raw is its canonical encoding.
@spec genesis() :: String.t()
The prev_hash of the first record of a chain: 64 zeros.
@spec seal(map(), String.t()) :: {:ok, iodata(), String.t()} | {:error, {:float, [String.t() | non_neg_integer()]} | {:unencodable, String.t()}}
Seal a stamped record onto the chain after prev_hash.
record may hold anything Jason encodes (atoms, structs with an encoder);
it is normalized through one encode/decode round trip first, so the hash is
computed over exactly what a reader will decode. Returns the line to write
(canonical JSON, no trailing newline) and the record's hash.