The problem
A firm has to screen counterparties against sanctions and restricted party lists before it does business. Months or years later an examiner, a court, or a counterparty's lawyer asks a question that sounds trivial: what exactly did you check, and against what. The honest answer from most firms is a screenshot and a database row saying a name was screened at a time against a list named by a publisher code and a date. Four things are wrong with that as evidence.
The list version is a label, and labels are not stable. Publishers amend files. A file published under one date can be corrected two days later and reissued under the same date. A firm that screened the earlier bytes has a row saying it screened that version, and so does a firm that screened the later bytes. Nothing distinguishes them. If the correction added the counterparty in question, that difference is the entire case.
The engine configuration is a moving target. Matching thresholds, transliteration rules and fuzzy matching parameters change constantly, usually to reduce alert volume. A clear verdict from a permissive ruleset and one from a strict ruleset are the same word in the log. The time is the firm's own clock, so a screening that appears to predate onboarding is only as good as the clock that wrote the row.
And the evidence itself is toxic to share. The one artifact that would settle the question contains the counterparty's identity, possibly a tax identifier or a wallet address, and possibly the list record that matched. It cannot go to a counterparty, a partner bank or an auditor without creating a data protection problem and, in the match case, tipping off.
Name the parties. The screening firm asserts it screened against the current list with an appropriate ruleset at the right time. The examiner asks for evidence and gets a report the firm produced. A correspondent bank relying on that screening asserts it relied reasonably, based on an assurance it cannot verify. The counterparty, if adverse, asserts it was never properly screened or was wrongly flagged. When screening runs as an autonomous pipeline, no human remembers the state of the system at eleven o'clock on a Tuesday in March. There is only what the system wrote down, and it wrote down a label.
What the receipt binds
The schema is at https://thehiveryiq.com/.well-known/schemas/screening-attestation-v1.json.
| Field | Meaning |
|---|---|
receipt_type | Fixed to screening.attestation. |
schema | Fixed to r1.0.0. |
screening_id | Identifier matching s_ plus up to 63 lowercase alphanumeric, underscore or hyphen. |
tenant_id | The owning tenant. |
attestation_kind | One of initial, rescreen or supersession. |
subject_commitment | The committed counterparty: scheme, a 64 hex value, a salt_id, a salt_epoch, a key_id, a fixed domain_separator and an identifier_class. |
counterparty_class | One of wallet, natural_person, legal_entity, vessel, aircraft or undisclosed. |
list_ref | The primary list: list_id, list_version, a list_digest, a digest_method, a record_count, an optional publisher_ref and up to 16 supplemental_lists. |
screened_at | The UTC instant of screening. |
anchor | The time reference: time_source, anchor_time and drift_bound_ms. |
engine | The screening system: system_id, ruleset_digest, ruleset_version and engine_key_id. |
verdict | One of clear, match, review or indeterminate. |
adverse_detail | Optional. match_strength_class, matched_list_id and a rationale_digest. |
jurisdiction_scope | One to 32 regime codes, each a two letter country code or EU or GLOBAL. |
ttl_seconds | The declared validity period, one second to 2592000 seconds. |
countersign | required_for, required_count, signed_count and up to eight signers, each with key_id, role, sig_b64u and signed_at. |
supersession_watch | Whether this attestation is being watched for supersession. |
supersession | Optional. reason, one to 256 prior_refs, salt_continuity, an optional salt_rotation_ref and scan_completeness. |
sequence | Position in an append only ledger: prev_hash and ledger_seq. |
evaluated_at | The UTC instant of evaluation. |
boundary | The fixed non attestation string. |
The digest_method enum matters. It is sorted_record_merkle_sha256 or sorted_record_concat_sha256, so two parties computing a list digest agree on construction as well as value.
What is deliberately not in the signed body
The counterparty is not named. subject_commitment.value is a 64 character lowercase hex keyed commitment under a scheme of hmac-sha256 or hmac-sha512-256, with a fixed domain_separator of
screening.attestation/subject/v1
That constant is checked by gate, so a commitment computed under a different domain cannot be presented here. The salt is identified by salt_id and salt_epoch and is not in the receipt, so a salt holder can confirm which counterparty this covers and a reader without it cannot enumerate candidates. No list record appears, and no match score. An adverse verdict carries match_strength_class as one of four buckets and a rationale_digest, so the reasoning is committed without being disclosed.
NO_SUBJECT_LEAK enforces the absence. It walks every string outside the exempt digest and signature fields and rejects six shapes: a run of 26 or more hexadecimal characters, a run of 26 or more base fifty eight characters, a nine digit grouped tax identifier form, a passport style alphanumeric run, an electronic mail address form, and a twenty character legal entity identifier form.
Countersignatures use their own domain separated string, with the fixed prefix
hive-countersign screening.attestation
followed by the screening_id, the subject commitment value, the primary list digest, the verdict and the signer's signed_at, joined by single spaces. The implementation documents why it uses those three identifying values rather than the envelope receipt identifier and payload digest: countersignatures live inside countersign.signers, so the payload digest would be a function of the signatures computed over it, and the receipt identifier is minted after they are collected. The verdict is bound explicitly, so a countersignature collected for a review verdict does not verify when moved onto a match verdict.
The receipt envelope uses the shared construction in src/typed/canon.js: canonical JSON with keys sorted recursively, no whitespace, non ASCII escaped as lowercase \uXXXX, lowercase hex SHA-256 over the UTF-8 bytes, and Ed25519 over the ASCII string hive-receipt <receipt_id> <payload_sha256> <ts>.
The gates
Verification runs in this order and stops at the first failure.
- SCHEMA. Requires the published schema plus the conditional presence and uniqueness rules the validator subset cannot express, such as a match verdict without adverse detail.
- ISSUER_KEY_MATCH. Requires the envelope key to resolve in the trusted keyring, to hold a screening engine role where the registry records roles, and to equal
engine.engine_key_id. - PAYLOAD_DIGEST, RECEIPT_ID, SIGNATURE and SIGNER_IDENTITY. The shared envelope layer. The digest of
signed_bodyis recomputed, the identifier has to parse and declare the body's type, the Ed25519 signature has to verify, and SIGNER_IDENTITY fails when only a caller supplied key verified. - ENGINE_IDENTITY_BINDING. Requires engine fields to be well formed and, when an engine registry is supplied, requires
ruleset_digestto equal the digest that registry records forruleset_version. - SUBJECT_COMMITMENT_FORM. Requires a 64 lowercase hexadecimal keyed commitment under a named scheme, the fixed domain separator, a well formed salt identifier and a non negative salt epoch.
- NO_SUBJECT_LEAK. Requires that no signed string outside the exempt digest and signature fields carries a plaintext counterparty identifier shape.
- LIST_VERSION_BINDING. Requires an independently obtained comparison digest for the named primary list identifier and version, an exact digest match, an agreeing record count, and a recomputable publisher register record when one is named.
- LIST_DIGEST_METHOD_AGREEMENT. Requires each declared digest construction to equal the construction the comparison source used for that list identifier and version.
- SUPPLEMENTAL_LIST_BINDING. Requires every supplemental list to bind to a comparison digest, to be uniquely named, and never to repeat the primary list identifier.
- ANCHOR_DRIFT_BOUND. Requires the anchor reading and the screening instant to lie within
drift_bound_ms, unless the time source is declaredunanchored. - VERDICT_AUTHORITY_QUORUM. Requires
countersign.required_countto equal the count recomputed from the verdict, one formatch, one forreviewand zero otherwise, requiresrequired_forto contain the verdict when that count is positive, and requires every countersignature to verify over the verdict bound signing string. - COUNTERSIGN_DISTINCTNESS. Requires countersigners to be distinct from the engine key, pairwise distinct, held by distinct controllers where the registry reports controllers, and never to sign before the screening instant.
- ADVERSE_DETAIL_CONSISTENCY. Requires an adverse verdict to attribute itself to a list actually bound here, and a non adverse verdict to carry no adverse detail.
- TTL_FRESHNESS. Requires the reliance instant to fall in the closed interval from
screened_atto the conservative expiry, computed asscreened_atplusttl_secondsseconds minusdrift_bound_msmilliseconds. - JURISDICTION_SCOPE_COVERAGE. Requires every regime the caller requires to appear in the declared scope, unless the scope declares
GLOBAL. - SUPERSESSION_LINKAGE. Requires every named prior attestation to be supplied, to recompute to the declared payload digest, and to agree on commitment, list version and verdict under the declared salt continuity.
- BOUNDARY_CONSTANT. Requires the signed boundary text to equal the published constant by exact character comparison.
The boundary
This receipt attests that a named screening engine, at a named ruleset version bound by a ruleset digest, evaluated a keyed commitment to a counterparty identifier against one named primary reference list at a named version bound by a digest over that version's contents, together with any named supplemental lists at their own named versions and digests, at a screening instant placed against a declared external time reference with a declared drift bound, and recorded the stated verdict under a recomputed signing authority requirement together with a declared validity period measured from that screening instant. It does not attest that the reference list itself is complete, accurate, current as published, or free of omission, and it does not attest that the publisher of that list acted correctly. It does not decide whether the counterparty is in fact a sanctioned, restricted or prohibited party, a false negative produced by an honest screening against a correctly bound list version remains a false negative that this receipt does not detect, does not correct and does not disclose, and no gate here detects an engine that reports a clear verdict where its own matching computation produced a match. It does not assess whether the screening program as a whole is adequate, whether the ruleset thresholds are appropriately calibrated, whether the set of lists screened is the correct set for any obligation, or whether the declared validity period is short enough for any purpose. It does not identify the counterparty, does not disclose any list record, does not disclose any match score, and does not confirm that the commitment key or the window salt has remained under the custody of any particular party. It does not authorize, block, freeze, reverse or settle any transaction, does not admit or reject any counterparty, does not constitute a report or notification to any authority, does not constitute legal, regulatory or compliance advice, and whether any obligation is satisfied by the screening it records is determined solely by the applicable law and by the parties' own arrangements.
BOUNDARY_CONSTANT compares that text against the published constant character by character, so it cannot be softened in a verifying receipt.
One clause is unusually blunt: no gate here detects an engine that reports a clear verdict where its own matching computation produced a match. A lying engine is outside the threat model. That is worth writing because it is true, and because it tells a compliance officer where to place the control this receipt does not provide.
An enumerated non attestation beats an implied claim in an examination. An examiner reading a broad assurance has to test the whole assurance. An examiner reading this boundary can see which questions the receipt answers and which remain live. The clause on list completeness works the same way. Reference lists have omissions. A receipt claiming a counterparty is not sanctioned would make a claim about the publisher's work that no screening firm can support, so this one claims something narrower: these exact list bytes were checked, and here is a digest anyone can compare.
Adversarial cases
From test/screening-attestation.test.mjs.
A stale list digest under a current version label fails LIST_VERSION_BINDING. This is the central attack and the central real world error. The label says the current version. The bytes screened were the previous ones. The gate needs an independently obtained comparison digest and compares exactly. A truncated record count fails the same gate, catching a partial file labelled complete. An unsupplied comparison digest source also fails it, so the gate refuses rather than trusting the declaration.
A digest method disagreeing with the comparison source fails LIST_DIGEST_METHOD_AGREEMENT. Same bytes, different construction, different digest. A repeated supplemental list identifier, and a supplemental entry repeating the primary list, both fail SUPPLEMENTAL_LIST_BINDING, so padding the apparent breadth of a screening does not work.
An engine key different from the envelope key fails ISSUER_KEY_MATCH, since the body cannot name one engine while another signs. A ruleset digest absent from the engine registry, and an unknown ruleset version, both fail ENGINE_IDENTITY_BINDING.
A short commitment value, a wrong domain separator, and an uppercase commitment value all fail SUBJECT_COMMITMENT_FORM. Commitments computed under another domain cannot be reused. An address like screening_id and an electronic mail address in tenant_id both fail NO_SUBJECT_LEAK, because free text fields that schema validation accepts are still walked for identifier shapes.
An anchor reading beyond the declared drift bound fails ANCHOR_DRIFT_BOUND. Declaring a tight bound and then reporting an instant outside it is self contradicting.
A match verdict declaring zero required countersignatures fails VERDICT_AUTHORITY_QUORUM. The required count is recomputed from the verdict, so a caller cannot declare its way out of a second signature. A signed count disagreeing with the signer array fails, an unsupplied countersigner registry fails, and a countersignature moved from a review verdict fails because the verdict is inside the countersign signing string.
Two key identifiers under one controller fails COUNTERSIGN_DISTINCTNESS. One person holding two keys is one person. A countersignature predating the screening fails the same gate. Adverse detail naming an unbound list fails ADVERSE_DETAIL_CONSISTENCY.
Reliance after the conservative expiry fails TTL_FRESHNESS, and reliance before the screening fails too. Reliance exactly at the expiry passes, which is tested explicitly. With no reliance instant supplied, the verifier substitutes the local clock and marks the result clock dependent rather than pretending it was anchored. Scope missing a required regime fails JURISDICTION_SCOPE_COVERAGE.
An unsupplied prior attestation, a prior commitment mismatch, a prior payload digest mismatch, and a list_version_update naming the same version all fail SUPERSESSION_LINKAGE. A rescreening chain has to chain.
Altered boundary text fails BOUNDARY_CONSTANT, and a post signature tamper fails the payload digest gate. Five positive cases confirm the shape: a clean clear verdict, a countersigned match verdict, same salt and rotated salt supersession records, and an indeterminate verdict that correctly requires no countersignature.
Performance
From hive-verifier-api/benchmarks/results-latest.json, measured at 2026-08-09T22:28:01.099Z at commit e167719f7d815809c6f55c5dde4495edf1c5615f over 200 iterations:
| Measure | Value |
|---|---|
| Mint p50 | 5.748 ms |
| Mint p95 | 6.257 ms |
| Verify p50 | 3.908 ms |
| Verify p95 | 7.281 ms |
| Artifact size | 4237 bytes |
Local harness measurement on one machine and one Node version, recorded on Node v20.20.1, linux/x64, two Intel Xeon cores at 2.90 GHz. It is a reproducible measurement of this code, not a production latency guarantee. This is among the largest artifacts of the deployed types in this run, which follows from a body carrying supplemental lists, countersigners and supersession refs.
How to run it
- Host:
https://thehiveryiq.com/v1 - Verify route:
/verify/screening-attestation, no credentials required - Mint route:
/mint/screening/attestation, requiresAuthorization: Bearer $HIVE_TYPED_MINT_TOKEN
curl -sS -X POST https://thehiveryiq.com/v1/verify/screening-attestation -H 'content-type: application/json' -d '{"receipt": <a receipt of this type>}'
Mint fails closed with 401 when the token is missing.
Where it sits in the canon
Family: compliance evidence. Category: typed receipt contract.
It chains to itself through supersession and sequence, so rescreenings over one commitment form a verifiable history rather than a pile of rows. It refers outward to a publisher register record through publisher_ref, which is where the list version binding gets its anchor. It does not replace a screening engine, a sanctions program, or a filing.
Status
production_deployed.