Integrity
An anchored event cannot be altered after the fact.
The whole point of HardState is that you never have to trust the institution, the API, or us. The Verifier
recomputes every link — from the raw event bytes all the way to a Bitcoin block header — in your browser, against
public explorers. A HardState Proof (.hardstate.json) bundles everything needed to check one event offline.
Integrity
An anchored event cannot be altered after the fact.
Attribution
It was signed by a key the issuer’s own log authorized at that moment.
Completeness
No anchored event can be removed or reordered without breaking the chain.
Time
It provably existed before a specific Bitcoin block.
What it does not prove: the truth of the content, or that an act which was never recorded did not happen.
Open app.hardstate.net/verify and drop in a .hardstate.json proof — or
open a receipt directly at https://app.hardstate.net/r/<hash>.
The Verifier runs entirely client-side (@hardstate/core, verifyProof). It shows each check below with its own
pass / fail, and reports the earliest confirmed Bitcoin block time as “existed before”.
A proof is a self-contained JSON bundle. batch, ots, and anchor are absent until they become available — fetch
the proof again later (GET /v1/proofs/<hash>) to complete it.
{ "hardstate": "proof", "hss": 1, "subject": { "kind": "event", "signed": { "event": {…}, "sig": {…} }, "hash": "…" }, "authority": [ /* issuer.create and the key.add / session.open establishing the signer */ ], "batch": { "id": "…", "root": "…", "size": 8, "index": 5, "path": ["…"] }, "ots": "<base64 of the detached .ots for batch.root>", "anchor": { "network": "bitcoin", "txid": "…", "root": "…", "size": 1, "index": 0, "path": [] }, "logUrl": "https://api.hardstate.net/v1/issuers/<id>/log", "issuedAt": "…"}A verifier performs — and should show — each step:
Integrity. SHA-256(canonical(subject.signed.event)) == subject.hash (canonical JSON is RFC 8785).
Signature & authority. Starting from issuer.create (self-signed), each key.add or session.open in
authority is signed by a key established earlier and allowed to sign it; the event’s actor was established at a
lower seq; its scope allows the event’s type and ts; and the signature verifies. For a session key, the
verdict names the person who opened it, and the session-expiry rule applies against Bitcoin time.
Batch. The leaf (0x01 ‖ hash for an event, 0x02 ‖ digest for a citizen stamp) combined with batch.path
recomputes batch.root — an RFC 9162 audit path.
OpenTimestamps. The .ots is a proof for batch.root; for each Bitcoin attestation at height h, the
attested message equals the merkle root of block h. The block header is fetched from at least two independent
sources that must agree.
Anchor. 0x03 ‖ batch.root with anchor.path recomputes anchor.root; transaction anchor.txid carries the
OP_RETURN script for anchor.root and is confirmed — same two-source rule.
Document (optional). With a file and its salt, the recomputed commitment is one of event.docs.
Completeness (optional, online). The issuer’s full log replays from seq 0 without error and holds this
exact event at its seq.
| Verdict | Meaning |
|---|---|
| invalid | Any performed step failed. |
| valid | Checks 1–3 pass and at least one of OpenTimestamps (4) or Anchor (5) passes on Bitcoin mainnet. |
| pending | Otherwise — e.g. the event is batched but not yet confirmed on Bitcoin. |
You do not have to use HardState’s Verifier at all. The OpenTimestamps path is a standard detached .ots over the
batch root, so any OpenTimestamps client works:
# Fetch the detached .ots for a batch and verify it against Bitcoin:curl -s https://api.hardstate.net/v1/batches/<id>/ots -o batch.otsots verify -d <batch_root_hex> batch.otsFor the daily HardState Anchor, read the transaction’s OP_RETURN on any block explorer and confirm the script is:
6a 24 48 53 53 01 <32-byte anchor root> # OP_RETURN PUSH36 "HSS" ‖ 0x01 ‖ rootthen recompute 0x03 ‖ batch.root up anchor.path to that root.
import { verifyProof } from "@hardstate/core";
const verdict = await verifyProof(proof);// → { status: "valid" | "invalid" | "pending", steps: [...], existedBefore?: "…" }