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.
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:
| Edition | provenance.text.package | provenance.text.sha256 |
|---|---|---|
| Ḥafṣ hafs | UthmanicHafs-v-3.0.zip | cdec7341b7c684e7… |
| Shuʿbah shubah | UthmanicShubah-v-3.0.zip | 28be7e6ac5b3af9b… |
| Warsh warsh | UthmanicWarsh-v-3.0.zip | 68e2f1c7a504a8dd… |
| Qālūn qalun | UthmanicQaloun-v-3.0.zip | d55082f903ec3975… |
| Dūrī duri | UthmanicDouri_V20.zip | 89fe6773ada1a8c1… |
| Sūsī susi | UthmanicSousi-v-3.0.zip | 1fe3764454650671… |
| Bazzī bazzi | UthmanicBazzi-v-3.0.zip | 63083df98ed0015a… |
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.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:
| Block | Repository read | Commit |
|---|---|---|
| quran-svg | quran-ws/quran-svg | 9fc0c87aebd7 |
| quran-text | quran-ws/quran-textprivate | 2aac0b1feefb |
| qiraat-ayah-map | quran-ws/qiraat-ayah-map | 7f38fc0e3c49 |
| quran-tajweed | quran-ws/quran-tajweedprivate | 992d1484a41e |
| quran-svg-elements | quran-ws/quran-svg-elementsprivate | de4a3b8c6b94 |
| quran-assets | quran-ws/quran-assetsprivate | 591e8f515228 |
| quran-engine | quran-ws/quran-engineprivate | cf021bbe3831 |
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
Recompute the digest over what you loaded
For the tajwīd edition, this is the exact computation the annotation file's
edition.sha256was 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/tajwidexposes the same computation aseditionDigestandassertEdition.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.
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
sha256infonts.jsonagainst the bytes and refuses to write on a mismatch.
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:
| Record | Example | Why |
|---|---|---|
| The block and its version | quran-text 0.1.0 | which format and which contract |
| The edition key | warsh | the riwayah changes the data, not the display |
| The source package, as the dataset names it | UthmanicWarsh-v-3.0.zip | the publisher's own identifier for the input |
| The digest the dataset published for it | provenance.text.sha256 | the check you ran, so a reviewer can rerun it |
| The commit or release you built from | a SHA or a tag | the 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.
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.
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.