Verifying a Genomarker Receipt
A Genomarker receipt is content-addressed. Its provenance hash is the SHA-256 of its RFC 8785 canonical JSON, computed with the integrity block removed. The expected value is the provenance hash shown on the receipt's public page, which the receipt's citation also carries. If the hash you recompute matches it, the JSON you hold is the receipt Genomarker published at that link.
How to verify
- Download the receipt's JSON: add
.jsonto its public link,/r/<token>.json. - Copy the provenance hash from the Verify section of the receipt's public page.
- Run one of the reference verifiers below with two arguments: the path to the JSON file and the provenance hash. It prints PASS and exits 0 when the hashes match, prints FAIL and exits 1 when they do not, and exits 2 if an argument is missing.
- Optionally, ask Genomarker to confirm the hash. On the same site that serves the receipt page, request
GET /api/receipts/by-hash/<provenance hash>— with or without thesha256:prefix. A published hash returnsfound: true, the receipt'spublic_urland itsstatus(published,unpublishedorrevoked); an unknown hash returns 404 withfound: false. Do not rely on a receipt whose status isunpublishedorrevoked.
What this proves
- Byte-equivalence: the receipt JSON you hold is canonically identical to the receipt Genomarker published at that link.
- Integrity of metadata: its parameters, software environment, dataset fingerprint and result hashes are unmodified.
What this does not prove
- Bit-replay: the receipt does not by itself let a third party re-run the analysis. Replay requires Genomarker's independent replay engine and its CLI — in development, mid-2027.
- Identity: the hash alone does not prove who issued a receipt. Genomarker's public lookup confirms that Genomarker published a receipt with exactly that hash, and its current status. Receipts from schema 1.8.0 onward also carry a detached ES256 signature over the rigor verdict (
integrity.signing); theverify.pyandverify.mjsin the receipt's.zipbundle check it offline against the keys published at/.well-known/genomarker-signing-jwks.json. Earlier receipts carry none.
Reference verifier — Python
pip install rfc8785import hashlib
import json
import sys
import rfc8785
if len(sys.argv) != 3:
print("usage: python3 verify_receipt.py RECEIPT_JSON PROVENANCE_HASH", file=sys.stderr)
sys.exit(2)
with open(sys.argv[1], "rb") as f:
payload = json.loads(f.read())
expected = sys.argv[2].strip().lower()
if not expected.startswith("sha256:"):
expected = "sha256:" + expected
subject = {k: v for k, v in payload.items() if k != "integrity"}
recomputed = "sha256:" + hashlib.sha256(rfc8785.dumps(subject)).hexdigest()
print("PASS" if expected == recomputed else "FAIL")
print(f"expected={expected}")
print(f"recomputed={recomputed}")
sys.exit(0 if expected == recomputed else 1)
python3 verify_receipt.py receipt.json sha256:<provenance hash>Reference verifier — Node
npm install canonicalizeimport { readFileSync } from "node:fs";
import { createHash } from "node:crypto";
import canonicalize from "canonicalize";
if (process.argv.length !== 4) {
console.error("usage: node verify-receipt.mjs RECEIPT_JSON PROVENANCE_HASH");
process.exit(2);
}
const payload = JSON.parse(readFileSync(process.argv[2], "utf8"));
let expected = process.argv[3].trim().toLowerCase();
if (!expected.startsWith("sha256:")) expected = "sha256:" + expected;
const subject = { ...payload };
delete subject.integrity;
const recomputed = "sha256:" + createHash("sha256").update(canonicalize(subject)).digest("hex");
console.log(expected === recomputed ? "PASS" : "FAIL");
console.log("expected=" + expected);
console.log("recomputed=" + recomputed);
process.exit(expected === recomputed ? 0 : 1);
node verify-receipt.mjs receipt.json sha256:<provenance hash>JSON Schema
The full schema for the receipt 1.x family (1.0.0, 1.1.0, 1.2.0, 1.3.0, 1.4.0, 1.5.0, 1.6.0, 1.7.0, 1.8.0, 1.9.0, 1.10.0, and 1.11.0) is published at https://genomarker.com/schemas/receipt-1.0.0.json. Minor bumps add optional fields only and keep this URL — 1.1.0 adds the optional method.handler_specific keys calibration_metrics, decision_curve_analysis, and subgroup_performance (emitted by model-training receipts). 1.2.0 adds the optional method.handler_specific.data_quality confounder pre-flight block (emitted by differential-expression receipts). 1.3.0 adds the optional method.handler_specific.analysis_classification field (one of "pre-specified", "post-hoc", or "exploratory") and the optional method.handler_specific.sap provenance block ({id, payload_sha256, zenodo_doi, locked_at, amendment_chain_depth}) — pre-registered analysis plan support. 1.4.0 adds the optional method.handler_specific.reporting_checklists block (TRIPOD+AI / REMARK / STARD-AI checklists, emitted by every inferential handler with applicable checklists) and the optional method.handler_specific.reporting_supplements block (frozen prose snapshot at publish-time). 1.5.0 adds the optional method.handler_specific.repro_score block (Reproducibility Score emission — composite 0-100 + letter A/B/C/D/F + 12 per-axis grades + evidence- link map). 1.6.0 adds the optional method.handler_specific.ingest_quality block (robust ingest — upload-time gene-ID harmonization + Excel repair + PHI screen + colData alignment + TPM rejection, with the tiered verdict hard-refuse / block-with-override / warn / ok). 1.7.0 adds the optional method.handler_specific.gate block (tiered gate and signed record — the composite severity-tiered gate verdict across confounder / colData / PHI / high-unmappable / ML-leakage / plan-amendment surfaces, plus the signed-override attribution when a block-with-override was waived). 1.8.0 fills the previously-reserved integrity.signing slot (RO-Crate / PROV emission — a detached ES256 JWS over the count-only rigor verdict, verdict_status "origin-signed-replay-pending" — PHI-free and offline-verifiable; null on legacy receipts). 1.9.0 adds the optional method.handler_specific.methods_parse block (Methods-Diff — the reproducibility-relevant metadata of the Claude Sonnet 4.6 LLM call that parsed external paper Methods prose into a typed MethodsCandidate — source + prose/system-prompt/parsed-candidate SHA-256s + parser provider/model/version + target handler type + user-confirm timestamp + token counts; emitted only by the methods_parse handler). 1.10.0 adds the optional method.handler_specific.sc_aggregation block (PHI-safe counts-only pseudobulk reduction, emitted by the sc_pseudobulk handler) and the optional method.handler_specific.mast block (MAST mixed-model method, emitted by the sc_mast handler) — single-cell differential expression. 1.11.0 adds the optional method.handler_specific.evidence_sources_used block (version-only evidence provenance — bundle_version + per-source {version, license} + bundle sha256, no gene list; embedded only at export time when a gene-evidence reference is included) — the gene evidence card. The schema URL is forever-stable; future major versions get new URLs.
Scope notes
The stdlib-only verifiers shipped inside receipt.zip use a hand-rolled canonicalization that is correct for typical payloads but does not cover every RFC 8785 edge case (exotic floats, surrogate-pair keys, U+2028/U+2029). For production verification or audit, prefer the reference snippets above.