> Source: https://upvera.tech/guides/verify-hash-chain/
> A hash chain links each record to the one before it by storing the previous record's digest. To verify it, start from the first record you trust, compute each record's digest in order, and check that the next record's "previous" field equals it. If any record was changed, the link to the next record breaks, and hiding that would mean rewriting every record after it. The runner on this page does this against a real public chain in your browser.

[Guides](/guides/)How-to

Guide · · For anyone checking · Day Roots

# How to verify a hash chain of daily records

Short answer

A hash chain links each record to the one before it by storing the previous record's digest. To verify it, start from the first record you trust, compute each record's digest in order, and check that the next record's "previous" field equals it. If any record was changed, the link to the next record breaks, and hiding that would mean rewriting every record after it. The runner on this page does this against a real public chain in your browser.

Run this check against the real repository

```
Press Run. Each day file is fetched from GitHub, made canonical, hashed and matched to the next day's prev.
```

## What a hash chain guarantees

Each record carries the digest of the record before it. Change one byte of an old record and its digest changes, so the next record's link no longer matches. The only way to hide an edit is to rewrite every later record too.

That is why a chain is usually paired with something that pins its latest link in public: a timestamp, a commit in a public repository, or a value other people have already copied. Once the tip is pinned, the history before it can't be rewritten quietly.

## What you need

-   **The first trusted value**, often called genesis. Every check starts from it.
-   **The records, in order**, as their exact bytes or as data with a canonical form so the bytes can be rebuilt.
-   **The digest rule**: which hash function, which bytes, and any prefix.

For Upvera's public chain these are `genesis.json`, one file per day under `days/YYYY/MM/`, and the rule `sha256("upvera/day/v1\n" + canonical JSON)`. All three are in [upvera-co/ledger-roots](https://github.com/upvera-co/ledger-roots).

## The check, in about 20 lines

```js
const RAW = "https://raw.githubusercontent.com/upvera-co/ledger-roots/main";
const sha = async (s) => [...new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(s)))]
  .map((b) => b.toString(16).padStart(2, "0")).join("");
// day files hold no arrays, so objects and primitives are enough here
const jcs = (v) => v === null || typeof v !== "object" ? JSON.stringify(v)
  : "{" + Object.keys(v).sort().map((k) => JSON.stringify(k) + ":" + jcs(v[k])).join(",") + "}";

const genesis = await (await fetch(`${RAW}/genesis.json`)).json();
let prev = genesis.hash;
let day = genesis.header.at.slice(0, 10);
for (;;) {
  const res = await fetch(`${RAW}/days/${day.slice(0, 4)}/${day.slice(5, 7)}/${day}.json`);
  if (res.status === 404) break;                     // reached the latest published day
  const text = await res.text();
  const rec = JSON.parse(text);
  if (text !== jcs(rec) + "\n") throw new Error(`${day}: not canonical`);
  if (rec.prev !== prev) throw new Error(`${day}: chain broken`);
  prev = await sha("upvera/day/v1\n" + jcs(rec));
  day = new Date(Date.parse(day) + 86400000).toISOString().slice(0, 10);
}
console.log("chain intact, tip", prev);
```

Paste it into a browser console, or press Run on this page, which runs the same logic and prints each link.

## Reading the result

-   **Every link matches.** No published day was edited, removed or reordered after the next one was written.
-   **"prev link broken".** The file before it changed, or a day was replaced. The first broken link tells you where.
-   **"not canonical".** The file isn't in the form its digest was computed over. It may have been reformatted, which is enough to fail.
-   **It stops early.** A missing day ends the walk. Days must be consecutive, so a gap is itself a finding.

## What a chain does not prove

A chain proves the records weren't changed after they were linked. It doesn't prove they were true when written, and it doesn't stop someone from rewriting the whole chain if nobody has a copy of its tip. Pin the tip somewhere independent: OpenTimestamps for the time, a public repository with protected history for the content, and your own saved copy for good measure.

## Questions

### Do I need to start from genesis every time?

Only once. After that you can save the latest digest you checked and continue from it next time.

### How is this different from a Merkle tree?

A chain orders records in time. A Merkle tree summarizes many records at once and lets you prove one of them is included. Upvera uses both: a tree per day, and a chain across days.

### What if the files move to another host?

The check doesn't depend on the host. Any copy of the files gives the same digests, so mirrors are easy to verify.

## How Upvera does it

Upvera seals its recorded events, such as Prime, comments and follows, and its UPV movements into minute blocks, folds each day into one root, and chains the days. The day files hold digests only. They are published to a public GitHub repository whose main branch blocks force pushes and deletions, and each file is timestamped with OpenTimestamps. The same check runs on the [Verify page](/verify/) and in the [Day Roots report](/roots/).

## Sources

-   [upvera-co/ledger-roots](https://github.com/upvera-co/ledger-roots)
-   [RFC 6962, Certificate Transparency (Merkle trees and logs)](https://www.rfc-editor.org/rfc/rfc6962.html)
-   [RFC 8785, JSON Canonicalization Scheme](https://www.rfc-editor.org/rfc/rfc8785.html)
-   [MDN, SubtleCrypto.digest()](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest)

## Related

[Tool**Canonicalize JSON (RFC 8785) and hash it**Turn any JSON into its RFC 8785 canonical form and SHA-256 it, in your browser, with a real example that links one day to the next.DevelopersUse it](/guides/json-canonicalization/)[Guide**How to verify an OpenTimestamps proof**Check an .ots proof with the official client, read pending and success results, and know what a timestamp proves and what it does not.Anyone checkingRead](/guides/verify-opentimestamps/)[UTR-26-03**Day Roots**Sealed every minute. Rooted every day.System reportRead](/roots/)[upvera.tech**Verify Upvera**Four checks you can run in your browser right now.ToolsOpen](/verify/)
