Detect sensor-record tampering with hashes
Build and verify a hash-chained ledger of fictional cold-chain readings, expose the exact failure caused by an altered record, and replay its Merkle root.

Cloud Clara: I want you to trace each digest, name the first broken rule, and keep record integrity separate from sensor truth.
Predict the reading, then compare it with the measurement.
Python 3 in your browser (JupyterLite)
Python · no installBuild and verify a hash-chained ledger of fictional cold-chain readings, expose the exact failure caused by an altered record, and replay its Merkle root.
Open the notebook in your browser and run each Python cell; no install or account is needed.
Three ways to run: use JupyterLite here with no install; run main.py locally from the downloadable lab folder; or open the same notebook in Google Colab.
Steps
Step 1
- Do
- Run `python3 main.py --step 1` to canonicalise one sensor record before hashing it.
- You will see
- The terminal prints the cold-001 fields, their compact sorted JSON, its full SHA-256 digest, and the rule that identical field ordering and values must reproduce it.
- Why it matters
- A verifier needs a stable byte representation; hashing an ambiguous serialization makes later comparison unreliable.

Step 1 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 2
- Do
- Run `python3 main.py --step 2` to append four fictional readings to a hash chain.
- You will see
- A four-row table shows every event and temperature beside its previous-hash and block-hash prefixes; the genesis and link rules follow the table.
- Why it matters
- Showing both hashes makes the chain relationship inspectable instead of presenting only a final head digest.

Step 2 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 3
- Do
- Run `python3 main.py --step 3` to verify the untouched chain.
- You will see
- Blocks 0 through 3 each show MATCH for content_hash and previous_link, followed by `Verifier: all content hashes and previous-hash links match.`
- Why it matters
- A known-good run proves the verifier checks both content and linkage before the deliberate failures are introduced.

Step 3 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 4
- Do
- Run `python3 main.py --step 4` to alter cold-003 from 5.4 C to 2.4 C without changing the stored hash.
- You will see
- The stored and recomputed 64-character hashes differ beside the hash rule, and verification reports `FAIL at block 2: stored hash does not match record content.`
- Why it matters
- The failed block and violated rule identify the first inconsistent record rather than merely reporting that the ledger is invalid.

Step 4 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 5
- Do
- Run `python3 main.py --step 5` to recompute only the altered block's hash and verify again.
- You will see
- Block 2 gets a new hash while block 3 retains the old previous_hash; verification reports `FAIL at block 3: previous_hash does not equal prior block hash.`
- Why it matters
- This demonstrates why hiding a historical edit requires rewriting every later link, not just the changed block.

Step 5 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 6
- Do
- Run `python3 main.py --step 6` to hash the same four records into a two-level Merkle tree.
- You will see
- The terminal prints four leaf prefixes, two parent prefixes, the full Merkle root, and `parent = SHA-256(left child hash + right child hash)`.
- Why it matters
- The intermediate hashes show how one compact root commits to the whole off-ledger batch.

Step 6 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab) Step 7
- Do
- Run `python3 main.py --step 7` to replay the Merkle root after changing cold-003.
- You will see
- The anchored and replayed roots differ beside `Rule: replayed root must equal the anchored root`, followed by `Verification FAIL` and the consistency-versus-truth scope warning.
- Why it matters
- A root mismatch detects changed evidence, while the scope line prevents treating hash consistency as proof that the physical probe was truthful.

Step 7 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
Chapter checks
These questions refer to the chapter’s examples. Use the return links to review their answers.
A gateway handles 10,000 meters that report once per minute. The team wants each raw reading to become a ledger transaction. Which redesign keeps the proof useful without turning the ledger into telemetry storage?
Return to the chapter’s knowledge checkA multi-party IoT system wants every raw sensor reading written directly to a shared ledger. What is the strongest architecture response?
Return to the chapter’s knowledge check