Data Integrity · Reading-level

Every reading is cryptographically sealed at the moment it arrives.

Most systems sign the report. FahCel signs every reading — and chains them together — so tampering with any value, anywhere in the history, becomes mathematically undeniable.

Hash algorithm
SHA-256 FIPS 180-4 · 21 CFR Part 11 §11.10(e)
Hashes per reading
Two data_hash · record_hash
Verification
Deterministic offline, replayable, open-source
Genesis
prev_hash = '0' one root per device lifetime
Before your next inspection

The cryptography does the work. You get the readiness.

Sealing happens at ingestion, so the proof already exists before anyone asks for it. For a QA manager, that turns three familiar scrambles into things that are simply ready.

Audit-ready pack in one click

Export a signed, timestamped PDF with the full reading-level history and its verification result — laid out for review against the records you're already assessed on.

EU GDP · 21 CFR Part 11 §11.10(e)

Excursion alerts in real time

The moment a reading crosses a threshold, the team is notified — and the record is already sealed into the chain, not reconstructed after the fact.

Sealed at ingestion · not at report time

Verification the auditor can replay

Hand over the dataset and the open verifier. An inspector can recompute the chain themselves, offline, and reach the same result you did.

Deterministic · offline · open-source
Evidence ledger · Live

Edit any reading. Watch the chain break.

Select a reading and change the temperature. Every record_hash from that point forward is recomputed and compared — the first broken link is flagged, and everything downstream is marked untrusted.
Device FC-L421 · Freezer 3 · Batch L2604-B Chain verified root: 0 · readings: 6
✓
6 of 6 readings verified. Every data_hash matches its payload; every record_hash links to its predecessor.
walk time · 0.4 ms
The principle

Two hashes per reading. One seals the data, one seals the history.

A single hash would tell you if a reading was edited in place. It wouldn't tell you if a reading was deleted, reordered, or silently inserted. Two hashes — one over the payload, one over the payload plus the previous record_hash — make every form of tampering detectable.

Layer 1 · Payload integrity

data_hash

SHA-256(deterministicJSON(// reading fields — proprietary
  { ████████ ┄ ████████ ┄ ████,
    ███████ ┄ ██████ ┄ █████ ┄ ████████,
    ████████ ┄ █████████ ┄ ███████,
    ██████████ ┄ ███████████ }

))

A fingerprint of the reading's business content, independent of its position in the chain. The exact field set and canonicalization rule are proprietary — but deterministic: the same reading always produces the same hash, on any system, forever.

Purpose. Tamper-detect the reading data in isolation. If any field changes — one decimal place, one second — the data_hash changes.
Layer 2 · History integrity

record_hash

SHA-256(deterministicJSON({
  ...business_fields,
  prev_hash // previous reading's record_hash
}))

The chain link. Every record_hash embeds the previous reading's record_hash, so the hashes form a one-way dependency graph. Genesis reading uses prev_hash = '0'.

Purpose. Tamper with any reading, and every record_hash after it changes. The chain tells you where tampering started.
Chain structure

Each reading depends on every reading before it.

A linked list where every link is independently verifiable. If reading #3 is altered, readings #4 through #N all fail verification — because their stored record_hash no longer matches the recomputed one.

Reading #001 · Genesis
-20.4 °C
prev_hash0
data_hasha17b…39fc
record_hashc4f0…91ae
prev_hash
Reading #002
-20.3 °C
prev_hashc4f0…91ae
data_hash7d22…8e01
record_hashe8a3…1c77
prev_hash
Reading #003
-20.6 °C
prev_hashe8a3…1c77
data_hashb91c…4a20
record_hash2f6d…d8b3
prev_hash
Reading #004
-20.5 °C
prev_hash2f6d…d8b3
data_hash03fe…6a15
record_hash91ba…47ec
Verification

verifyChain() — deterministic, replayable, offline.

The verifier is deterministic and identical to the code that seals the chain: the same readings always produce the same verdict. Customers and auditors run the full version offline against any exported dataset — the core sealing rule stays proprietary.

// hashChain.js
export function verifyChain(readings) {
  const sorted = [...readings].sort(
    (a, b) => a.device_timestamp - b.device_timestamp
  );

  let prev_hash = sorted[0].prev_hash; // anchor ('0' at genesis)
  let firstTampered = null;

  for (const r of sorted) {
    core sealing — proprietary, redacted
    const data_hash   = sha256(canon(██████████));
    const record_hash = sha256(canon({ ████████ ┄ ████,
      prev_hash, ████████████ }));

    if (data_hash   !== r.data_hash ||
        prev_hash   !== r.prev_hash ||
        record_hash !== r.record_hash) {
      firstTampered ??= r.seq;         // remember first failure
    }
    prev_hash = r.record_hash;         // walk forward regardless
  }

  return {
    valid: firstTampered === null,
    first_tampered_at: firstTampered,
    total: sorted.length,
  };
}
1

Order by device timestamp

The verifier walks readings in device_timestamp order — not ingestion order. A reading that arrives late but was captured earlier slots into the right position in the chain.

2

Recompute both hashes from scratch

For every reading, the verifier rebuilds data_hash and record_hash from the stored fields using the same canonicalization rules. It does not trust the stored values — it recomputes them and compares.

3

Track the first broken link

The first mismatch — in either hash — is flagged. Every reading after it inherits suspicion: even if their own hashes look valid, the chain has already lost integrity.

4

Return a verifiable verdict

valid, first_tampered_at, and total readings walked. The same values land in your audit PDF — so the inspector sees exactly what your QA team saw, and can replay it themselves.

Common questions

What auditors and QA leads ask before they trust the chain.

Why does each temperature reading need two hashes?

A single hash detects a reading edited in place, but not one deleted, reordered or silently inserted. FahCel stores two: a data_hash over the reading’s payload, and a record_hash over that payload plus the previous reading’s record_hash. Together they make every form of tampering detectable, not just field-level edits.

What is a record_hash in a cold-chain log?

The record_hash is the chain link. It is the SHA-256 of the reading’s canonical payload combined with the previous reading’s record_hash, so the hashes form a one-way dependency graph. The first reading in a shipment uses prev_hash = '0'. Alter any reading and every record_hash after it changes.

How do you verify a temperature chain has not been altered?

Run verifyChain() over the exported readings. It recomputes each data_hash from the payload and each record_hash from the payload plus its predecessor, then compares them to the stored values. The check is deterministic, replayable and works offline, so an auditor can reproduce the result without trusting FahCel.

What happens if someone edits a past temperature reading?

Verification fails from that reading onward. If reading #3 is altered, readings #4 through #N all fail because their stored record_hash no longer matches the recomputed one. The first broken link is flagged and everything downstream is marked untrusted, so you learn where the tampering started, not just that it happened.

Which standards does the hash chain map to?

Hashing uses SHA-256 as specified in FIPS 180-4. The tamper-evident audit trail is designed against 21 CFR Part 11 §11.10(e), which requires secure, computer-generated, time-stamped audit trails that do not obscure previously recorded information, and it supports EU GDP record-keeping expectations.

Watch the chain hold — or break. Live.

In a 20-minute QA demo, we'll seal a stream of temperature readings at ingestion, run verifyChain in front of you, then tamper with a single reading and show you exactly where the chain catches it. No slideware — the actual system, the actual hashes.

// verify against your own datalogger's API
$ fahcel verify readings.csv
→ 14,208 readings walked
→ genesis: prev_hash = '0'
→ chain: verified ✓

// alter one reading, re-run
$ fahcel verify readings_edited.csv
→ integrity breach at reading 8,341
→ chain: BROKEN ✗