Skip to content

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
samples/typescript/verify-rows.ts
// 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}`);

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 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 sandbox verifies 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.

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 the ai_policy_bits a 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.

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.