Skip to main content

What you can request

A request is a DCQL query, which names the credential and the claims to disclose, plus optional predicates, an optional nullifier and a purpose. The sandbox verifier accepts only requests on its allowlist, so a requester cannot ask for more than the sandbox permits. This page describes the sandbox police-certificate credential and what the allowlist lets you ask for.

The code on this page uses the verifier SDK. Requesting and reading proofs covers installing it and calling it from a Next.js app.

The sandbox police-certificate credential​

This section is the synthetic sandbox police-certificate attestation:

PropertyValue
Schemasandbox.tridentgold.pcoc-attestation, version 1, schema uid 4096
Format and suitezknc, ZKNC-1
IssuerThe sandbox issuer, issuer id 17

It is a synthetic sandbox credential. It describes no real person and is not an authoritative Police Certificate of Character.

It has four claims:

ClaimTypeWhat it holdsDiscloseProve
record_statusenumThe outcome of the synthetic record check. In this schema clear is the only value it can take.YesEquals clear (equality or enum), or is in a set of values (enum-set)
decision_datedateThe date of the synthetic decision.NoFalls within a date range (range). The range must span at least 30 days.
reference_nostringA synthetic reference number.NoNothing
subject_refreferenceA reference to the synthetic subject record.NoNothing

reference_no and subject_ref cannot be requested at all: they cannot be disclosed and no predicate may use them. decision_date cannot be disclosed or compared for equality; only a range at least 30 days wide is allowed, so a requester cannot narrow the date down to a single day.

Build a request with the SDK​

policeCertificate() builds a request for this credential:

import { policeCertificate } from "@human.tech/tridentgold-zknc-mvp-verifier";

const input = policeCertificate({
purpose: "Confirm a clear record for volunteer screening",
disclose: ["record_status"],
decidedWithin: { days: 180 },
nullifier: { scopeLabel: "volunteer-screening" }
});
OptionWhat it asks forWhere it appears in a verified result
purposeRequired. Shown to the person on the consent screen. One line of plain text, at most 140 characters.
disclose: ["record_status"]Disclose record_status.result.disclosed.record_status
recordStatus: "clear"Prove record_status is clear without disclosing it.result.predicates.record_status_clear
decidedWithin: { days }Prove decision_date is within the last days days, up to and including today (UTC). days must be at least 30.result.predicates.decision_date_in_range
decidedBetween: { from, to }Prove decision_date is between two ISO dates (YYYY-MM-DD), inclusive. Use instead of decidedWithin.result.predicates.decision_date_in_range
nullifier: { scopeLabel }Ask for a nullifier under this scope label. See Nullifiers.result.nullifier

The builder returns plain data in the raw form below, so you can log it, inspect it or extend it.

Write the same request as raw DCQL​

requestProof() also accepts a DCQL query and predicates directly. This is the same request as the builder call above:

const DAY_MS = 86_400_000;
const today = new Date().toISOString().slice(0, 10);
const from = new Date(Date.now() - 180 * DAY_MS).toISOString().slice(0, 10);

const input = {
purpose: "Confirm a clear record for volunteer screening",
dcqlQuery: {
credentials: [
{
id: "pcoc",
format: "zknc",
claims: [{ id: "record_status", path: ["record_status"] }]
}
]
},
predicates: [
{
id: "decision_date_in_range",
path: ["decision_date"],
op: "range" as const,
lo: from,
hi: today
}
],
nullifier: { scopeLabel: "volunteer-screening" }
};
  • dcqlQuery is a standard DCQL query and is sent as written. In this sandbox every DCQL claims entry is a disclosure.
  • meta may be omitted, as here: the verifier fills in the sandbox schema, suite and issuer. If you include it, it must match them.
  • predicates each have an id you choose, a path naming one claim, and an op: equality or enum with value, enum-set with codes, or range with lo and hi (inclusive). Dates are YYYY-MM-DD; enum values are their names, such as clear.
  • A verified result reports each predicate under the id you chose.
  • Credential, claim and predicate ids are 1 to 64 characters of letters, digits, _ and -. The verifier answers any other id with request_invalid.
  • A predicate missing an operand its op requires (value for equality and enum, lo or hi for range, codes for enum-set), or carrying one its op does not use, is also answered with request_invalid, with no rule. Operands that are present but outside the allowlist get the rules below.

See what was sent​

The request that requestProof() returns carries, in request.request, the parts of the signed request you control, as signed. Its dcqlQuery is the DCQL query the wallet received, including the meta the verifier filled in:

{
"purpose": "Confirm a clear record for volunteer screening",
"dcqlQuery": {
"credentials": [
{
"id": "pcoc",
"format": "zknc",
"meta": {
"suite": "ZKNC-1",
"schemas": [
{ "id": "sandbox.tridentgold.pcoc-attestation", "uid": "4096", "version": 1 }
],
"issuer_ids": ["17"]
},
"claims": [{ "id": "record_status", "path": ["record_status"] }]
}
]
},
"predicates": [
{
"id": "decision_date_in_range",
"path": ["decision_date"],
"op": "range",
"lo": "2026-03-27",
"hi": "2026-09-23"
}
],
"nullifier": { "scope": "trident-gold-integrator-demo.vercel.app/volunteer-screening" }
}

The DCQL query keeps its standard member names, such as issuer_ids; the fields around it use the SDK's camelCase names.

Nullifiers​

A nullifier is a value derived from the holder and a scope. The same holder answering requests with the same scope label gives the same nullifier each time; a different scope label gives an unrelated value. It reveals nothing else about the holder. Use it to recognize a returning holder, for example to notice that the same holder has already registered for a programme.

Ask for one with nullifier: { scopeLabel }. The label is lowercase letters, digits, _ and -, starting with a letter or digit, at most 63 characters. The verifier prefixes its namespace, so the full scope is trident-gold-integrator-demo.vercel.app/<scopeLabel>, echoed as request.request.nullifier.scope. A verified result then carries the value in result.nullifier.

The allowlist​

Beyond the per-claim rules above, a request must:

  • ask for exactly one credential, the sandbox police-certificate attestation, and not use DCQL credential_sets or DCQL values matching;
  • disclose at most 3 claims;
  • use at most 4 equality predicates, and at most one per claim;
  • use at most 1 range predicate, and at most 1 enum-set predicate with at most 8 values;
  • carry a purpose: one line of plain visible text, at most 140 characters, with no leading or trailing spaces or control characters.

The allowlist is machine-readable. getPolicy() returns it (verifier is the client from Create the client):

const policy = await verifier.getPolicy();
policy.claims.decision_date; // { type: "date", disclose: false, ops: ["range"], minRangeDays: 30 }

When requestProof() can fetch this policy and it is policy_version 1, the SDK checks the request against it before sending. Otherwise it sends the request unchecked. The verifier always checks it. A request outside the allowlist throws ZkncRequestNotPermittedError, whose rule names the rule that failed:

RuleMeaning
credential.countThe query does not ask for exactly one credential.
credential.format, credential.schema, credential.issuerThe credential's format, suite, schema or issuer is not the sandbox's. Omit meta to use the sandbox's.
credential_sets.forbiddenDCQL credential_sets are not supported.
claim.unknown, claim.path_invalidA path does not name exactly one known claim, or a claim is disclosed twice.
claim.values_forbiddenDCQL values matching was used, or an equality or enum predicate's value is one the claim cannot take.
<claim>.disclose_forbiddenThat claim cannot be disclosed, for example decision_date.disclose_forbidden.
<claim>.op_forbiddenThat predicate operation is not allowed on that claim.
predicate.id_duplicate, predicate.op_unknownTwo predicates share an id, or an op is not one of the four.
range.countMore than one range predicate.
range.bounds_invalidA bound is not an ISO YYYY-MM-DD date or not a real calendar date, or lo is after hi.
range.too_narrowhi is less than 30 days after lo.
set.countMore than one enum-set predicate.
set.codes_invalidcodes is empty, or holds a value that is not allowed.
limits.disclosures, limits.equalitiesToo many disclosures or equality predicates.
nullifier.label_invalidThe scope label does not match the pattern above.
purpose.missing, purpose.too_long, purpose.invalid_charactersThe purpose is absent, over 140 characters, or not one line of plain text.