Provenance and digests

For this text, where a copy came from matters as much as what it says. A dataset that cannot name its source is not one a reviewer will accept, and a copy you cannot verify is one you cannot trust after the first divergence. This page is about the two records every dataset here ships — the source, and the hash — and how to use them.

Time
About 8 minutes

The two solve different problems. Provenance answers which published thing is this derived from — a question for a reviewer, an auditor, or you in a year's time. A digest answers is the file I am holding byte-for-byte that thing — a question for your code at load time. You need both: a digest without a named source proves you have an exact copy of nobody-knows-what, and a source without a digest cannot catch a substituted text.

What each dataset records

Quran Text: the publisher package, per edition

Every muṣḥaf file carries a provenance.text block naming the digital package it was extracted from and that package's SHA-256:

Editionprovenance.text.packageprovenance.text.sha256
Ḥafṣ
hafs
UthmanicHafs-v-3.0.zipcdec7341b7c684e7…
Shuʿbah
shubah
UthmanicShubah-v-3.0.zip28be7e6ac5b3af9b…
Warsh
warsh
UthmanicWarsh-v-3.0.zip68e2f1c7a504a8dd…
Qālūn
qalun
UthmanicQaloun-v-3.0.zipd55082f903ec3975…
Dūrī
duri
UthmanicDouri_V20.zip89fe6773ada1a8c1…
Sūsī
susi
UthmanicSousi-v-3.0.zip1fe3764454650671…
Bazzī
bazzi
UthmanicBazzi-v-3.0.zip63083df98ed0015a…

Read at build time from public/demo/fatiha.json, which carries each muṣḥaf file’s own provenance.text block from quran-text. The hash is of the publisher’s package, so it is the same for every ayah of that edition. Truncated here for width; the full value is in the payload.

The hash is of the publisher's package, not of the muṣḥaf file, so it proves the build started from the right input. The dataset's own outputs are covered separately by data/manifest.json, which lists the SHA-256 of every source package and every emitted file, and each file lists every place the build departed from its package.

Quran Tajweed: the digest of the text the spans describe

The annotation file carries no text — only positions — so it must say exactly which text they index:

Edition: uthmani-hafsCorpus: v0.4.2Ayat: 6,236edition.sha256: b5d29736bb3ef49d…

That digest is taken over the content, not the file bytes: each reference and its text in muṣḥaf order, so re-indenting the JSON does not change it, and hashing the file gives a different value.

Quran SVG Elements: a checksum for every file, and the commits it was built from

The release tarball ships CHECKSUMS.txt covering every file the build produced, and VERSION.json recording the artwork and pipeline commits. Verify with shasum -a 256 --ignore-missing -c CHECKSUMS.txt; the flag is required because the checksum list also covers pre-compressed copies the tarball omits.

This site: the commit every payload was built from

Every file under public/demo/ was built by one script from these commits, and re-running it changes the SHA:

BlockRepository readCommit
quran-svgquran-ws/quran-svg9fc0c87aebd7
quran-textquran-ws/quran-textprivate2aac0b1feefb
qiraat-ayah-mapquran-ws/qiraat-ayah-map7f38fc0e3c49
quran-tajweedquran-ws/quran-tajweedprivate992d1484a41e
quran-svg-elementsquran-ws/quran-svg-elementsprivatede4a3b8c6b94
quran-assetsquran-ws/quran-assetsprivate591e8f515228
quran-enginequran-ws/quran-engineprivatecf021bbe3831

Read at build time from public/demo/SOURCES.json, generated 2026-09-12. This is the site’s own provenance record: every demo payload was built from these commits, and re-running the sync changes the SHA, which is the review.

The site's provenance registry lists source, version, digest and status for every dataset in one place.

Verify at load time

  1. Recompute the digest over what you loaded

    For the tajwīd edition, this is the exact computation the annotation file's edition.sha256 was produced with. Run against the shipped edition it prints the value shown above.

    import { createHash } from "node:crypto";
    
    const refs = Object.keys(edition.ayahs).sort((a, b) => {
      const [sa, aa] = a.split(":").map(Number);
      const [sb, ab] = b.split(":").map(Number);
      return sa - sb || aa - ab;
    });
    
    const canonical = refs
      .map((ref) => `${ref} ${edition.ayahs[ref]}`)
      .join("");
    
    const digest = createHash("sha256").update(canonical, "utf8").digest("hex");
    if (digest !== annotations.edition.sha256) {
      throw new Error("this is not the text these offsets describe");
    }
    

    @quran.ws/tajwid exposes the same computation as editionDigest and assertEdition.

  2. Fail loudly, at startup, not at render

    A mismatch means every position in the annotation file is suspect. Throw before the first ayah renders. A warning in a console nobody reads is the same as no check.

  3. Verify what you download, once

    For release tarballs, run the checksum list after download and before you serve anything from it. For the fonts this site ships, the sync script recomputes each sha256 in fonts.json against the bytes and refuses to write on a mismatch.

A digest of the wrong thing passes and proves nothing

Hash the tajwīd edition file and compare it to edition.sha256, and the check fails on a correct file — so the temptation is to remove the check. Hash Quran Text's output file and compare it to provenance.text.sha256, which covers the publisher's input package, and the same thing happens. Each digest names what it covers. Read that before you write the comparison, and never loosen a check to make it pass.

What to persist with anything you ship

Enough that someone can answer "where did this text come from" without re-deriving it:

RecordExampleWhy
The block and its versionquran-text 0.1.0which format and which contract
The edition keywarshthe riwayah changes the data, not the display
The source package, as the dataset names itUthmanicWarsh-v-3.0.zipthe publisher's own identifier for the input
The digest the dataset published for itprovenance.text.sha256the check you ran, so a reviewer can rerun it
The commit or release you built froma SHA or a tagthe only unambiguous "which one"

Store it in your build output, not only in a README, because a manifest travels beside every file (Versioning and corrections, rule 1.2) and what you depend on is pinned to a tag (rule 6.3). The site does this with public/demo/SOURCES.json: it is generated by the same script that generates the data, so it cannot describe a different build.

Two things this does not prove

A digest proves the file is the one the dataset was built from. It does not prove the dataset is faithful to a printed muṣḥaf — Quran Text says plainly that its checks prove fidelity to the publisher's packages, not the packages' fidelity to print. And a dataset that records a dirty worktree in its version file — Elements v1.0.0 does — has published bytes that cannot be traced to one commit, however good its checksums. Provenance is only as strong as its weakest named link.

Try it live

Every dataset this site demonstrates, with its source, version, digest, status and the transformations between source and data.

How this data is made

Each dataset records its source at the point it is built, by the same process that builds it: a provenance block written by the extractor, a content digest written by the annotation run, a checksum list written by the bundler. Provenance that is generated cannot drift from the data it describes; provenance that is typed can.