Verify what you received
Everything MasterDB serves can be checked without trusting MasterDB, and everything needed to check it is public: the signed key set, certificates, projection specifications, the transparency log. Three questions, three answers:
| Question | Check | Where |
|---|---|---|
| Is this the real business? | Its certificate, signed by MasterDB’s issuance key, chained to the pinned trust anchors | offline: verifyCertificate / verify_certificate |
| Is this row what MasterDB projected? Is this what MasterDB served me? | The row signature and the receipt | offline: verifyServedRow, verifyReceipt / verify_served_row, verify_receipt |
| Is this record what the business published? | The seal over the exact bytes, the certificate and AI policy version in force when it was sealed, the scope, the log inclusion | POST /v1/verify, public, no account |
Rows and receipts, offline
Section titled “Rows and receipts, offline”// Verify a search offline: every row's projection signature and the receipt that lists them.// Answering from rows alone is allowed; this is how you know the rows are what MasterDB projected.import assert from 'node:assert/strict';import { createPrivateKey } from 'node:crypto';import { readFileSync } from 'node:fs';import { SANDBOX, createEd25519Signer, createRetrievalClient } from '@masterdb/client';import { KeySet, SANDBOX_ANCHORS, verifyReceipt, verifyServedRow } from '@masterdb/verifier';
const baseUrl = process.env.MASTERDB_API_URL ?? SANDBOX.retrieval;const signer = createEd25519Signer(createPrivateKey(readFileSync(process.env.MASTERDB_KEY_FILE as string)));const client = createRetrievalClient({ baseUrl, signer });const anchors = process.env.MASTERDB_ANCHORS_FILE ? JSON.parse(readFileSync(process.env.MASTERDB_ANCHORS_FILE, 'utf8')) : SANDBOX_ANCHORS;const keys = await KeySet.fetch(baseUrl, { anchors, sandbox: true });
const { data, error } = await client.POST('/v1/search', { body: { collection: 'events', filter: { all: [{ country: 'US' }] }, sort_by: 'published_at:desc', limit: 20 },});if (error) throw new Error(error.code);
for (const row of data.rows) { // Refuses (VerificationError with a reason) if a byte of the signed fields differs from what was projected. const v = verifyServedRow(row, keys); console.log('row', v.record_id, 'projection', v.projection.version, 'origin', v.adl_origin.slice(0, 20));}
// The receipt lists each row's id, origin hash and signature: passing the rows checks they match it.const receipt = verifyReceipt(data.receipt, keys, { rows: data.rows });assert.equal(receipt.rows.length, data.rows.length);assert.equal(receipt.country, 'US');console.log(`${receipt.rows.length} rows verified; receipt ${receipt.retrieval_id}`);"""Verify a search offline: every row's projection signature and the receipt that lists them.Answering from rows alone is allowed; this is how you know the rows are what MasterDB projected."""
import jsonimport os
import httpxfrom masterdb_signing import SANDBOX_API, MasterDBAuth, load_keyfrom masterdb_verifier import SANDBOX_ANCHORS, KeySet, parse_strict, verify_receipt, verify_served_row
base_url = os.environ.get("MASTERDB_API_URL", SANDBOX_API)client = httpx.Client(base_url=base_url, auth=MasterDBAuth(load_key(os.environ["MASTERDB_KEY_FILE"])))anchors = json.load(open(os.environ["MASTERDB_ANCHORS_FILE"])) if "MASTERDB_ANCHORS_FILE" in os.environ else SANDBOX_ANCHORSkeys = KeySet.fetch(base_url, anchors=anchors, sandbox=True)
res = client.post("/v1/search", json={"collection": "events", "filter": {"all": [{"country": "US"}]}, "sort_by": "published_at:desc", "limit": 20})res.raise_for_status()# Parse strictly: a row is verified over the values exactly as served, numbers included.data = parse_strict(res.content)
for row in data["rows"]: # Raises VerificationError (with a reason) if a byte of the signed fields differs from what was projected. v = verify_served_row(row, keys) print("row", v.record_id, "projection", v.projection["version"], "origin", v.adl_origin[:20])
# The receipt lists each row's id, origin hash and signature: passing the rows checks they match it.receipt = verify_receipt(data["receipt"], keys, rows=data["rows"])assert len(receipt.rows) == len(data["rows"]) and receipt.country == "US"print(len(receipt.rows), "rows verified; receipt", receipt.retrieval_id)verifyServedRow reads the row’s projection version from adl_proj, removes the members set at serve time (ai_policy_bits, sponsored), and checks adl_row_sig with MasterDB’s projection key over the RFC 8785 canonical form. verifyReceipt checks the receipt’s signature by a receipt key of the region that served you, valid at served_at, and — given the rows — that each row matches its receipt entry. An unknown projection version or payload type is refused loudly, never guessed.
The trust anchors and the key set
Section titled “The trust anchors and the key set”The verifier trusts nothing but its anchors: MasterDB’s root keys. It fetches the key set from GET /.well-known/keys (the only network call the libraries make) and accepts a key only if its certificate chains to an anchor, following root succession by cross-certification. With a saved key set, everything runs offline.
- Production anchors are pinned in the verifier libraries, from MasterDB’s root key ceremony; sandbox anchors are an explicit opt-in (
sandbox: true). You can also pass anchors explicitly. - A certificate or key marked
sandboxverifies only under the sandbox anchors, so sandbox data can never pass as production. - Long-lived artefacts — certificates, key certificates, the key set — carry two signatures, the classical one and ML-DSA-65, and the verifier libraries require both. Rows and receipts are Ed25519 only.
Records: the public verify endpoint
Section titled “Records: the public verify endpoint”POST /v1/verify takes the record’s bytes (base64), its seal and, optionally, the sidecar and a row, and answers:
seal: whether the seal is valid for these bytes, by which key, under which certificate and AI policy version, sealed when, with each check named (certificate,ai_policy,scope,key_event);log_inclusion: the seal’s leaf in the transparency log, with the checkpoint;projection: given a row, whether the row signature is valid and whether re-running the projection on the record gives that row;ai_policy_bits: given theai_policy_bitsa row carried, with the business’s sealed AI policy record as the record, whether the bits are the ones its named booleans derive, and which contexts it blocks;statement: all of the above, signed by MasterDB’s statement key.
The seal key’s own log leaf. Every key a business adds to its register is a key_added leaf in the transparency log. The endpoint proves the seal key’s leaf from the log: checks.key_event is ok, with key_event saying where the leaf is, or missing, with a line in warnings, and the seal stands. Send "key_events": "require" to have a missing leaf refuse the seal (key_event_missing), as the libraries’ keyEvents: 'require' does. A key added in the last hour is not sequenced yet.
A failed check is a 200 with valid: false and a reason. The endpoint needs no account and is never rate-limited so as to block a checker. It requires the record itself — a hash or a bare id is not enough — so it cannot be used to learn which ids exist.
Checking a seal from public data alone. A seal names a key of the business’s key register — which of its people’s passkeys and which integration keys may seal. Each business’s public sealing and integration keys, current and past, are published beside its certificate (GET /v1/certificates/{uuid}/keys, signed by MasterDB’s statement key), so the libraries check a seal offline from them (publishedKeys / published_keys); the public endpoint checks it against the register.
What verification does not prove
Section titled “What verification does not prove”A seal proves who published the bytes and when, not that they are true. A receipt proves what MasterDB asserted it served; it does not prove the response arrived. See the security model.