Audit-challenge protocol
How a regulator issues a signed predicate-scoped challenge over an agent population and verifies the response — inclusion proofs against committed Signed Tree Heads anchored on the public Sigstore Rekor log, without trusting Iqrar's infrastructure.
The audit-challenge protocol is the regulator-facing surface of Iqrar's commitment ledger. It lets an authority — DFSA, ESMA, FCA — verify agent behaviour cryptographically, end-to-end, without trusting the operator running the agent or Iqrar itself.
What the regulator submits
A Challenge is a signed envelope { body, sig } where body carries:
| Field | Purpose |
|---|---|
id | Globally unique challenge id (UUID or hash-derived). |
issuer_kid | The authority's kid in the foundation registry. |
predicate.jurisdiction | Match agents whose declared jurisdiction equals this. The issuer's registered scope must encompass it. |
predicate.capabilities[] | Optional. Match agents declaring at least one of these capabilities. |
predicate.time_window | { start, end } in ms — the inclusive ts window over the audit chain. |
predicate.action_types[] | Optional event-type filter (e.g. ["decision.made", "human_review.recorded"]). |
sampling.method | "all" or "random". |
sampling.rate, sampling.seed | Required when method: "random". The seed drives a deterministic SHA-256 PRF the regulator can recompute. |
bound_directive_id | Optional. When set, the response also returns inclusion proofs for the directive's lifecycle audit-chain entries. |
issued_at | Issuance wall-clock time. |
The regulator signs canonical(body) with their private key. The CLI helper does this for you:
$ bun run challenge:issue
--issuer DFSA
--jurisdiction AE-DIFC
--since '24h ago'
--sample-rate 0.1
--bound-directive dir-3a91b...c4e
--publish▸ Loaded DFSA private key from ~/.iqrar/keys/dfsa.json ▸ Built canonical body (4 predicates, sampling=random rate=0.1) ▸ Signed with Ed25519 ▸ POST https://api.iqrar.io/audit/challenge ▸ challenge_id: chl-7f3a... ▸ matched_agents: 12 ▸ proofs returned: 87 ▸ directive_audit.audit_chain_entries: 24
What the worker does
For every challenge the worker, atomically:
- Verifies the signature against the pinned foundation registry.
- Scope-checks the predicate's jurisdiction against
authorities[issuer_kid].scope.jurisdictions. A regulator cannot challenge an agent outside their remit. - Capability-checks that the issuer holds the
audit_challengecapability. - Resolves the matching agents by joining the predicate against the agents table (
agent_id IN ...,org = ...,jurisdiction = ..., capability join via SQLitejson_each). - Loads matching
audit_entrieswithin the time window + action-type filter. - Applies the deterministic sample —
sampleDeterministic(candidate_keys, rate, seed)— using SHA-256 as a PRF over(seed, agent_id, seq). The regulator can recompute the expected sample independently. - Builds inclusion proofs for every sampled entry against the smallest STH that covers it (per agent), grouped per agent.
- Persists the challenge in
audit_challengeswith the result so the directive→challenge investigative chain is itself recorded. - Returns
{ proofs[], directive_audit?, rekor_url }.
Every step is committed: a rejected challenge is recorded with status: 'rejected', a verified challenge with status: 'verified'. The challenge transcript is itself an auditable artifact.
What the response looks like
{
"ok": true,
"challenge_id": "chl-7f3a...",
"matched_agents": ["acme-bot-1", "acme-bot-7", "..."],
"matched_entries": 871,
"sampled": 87,
"proofs": [
{
"agent_id": "acme-bot-1",
"seq": 412,
"entry": {
"agent_id": "acme-bot-1",
"seq": 412,
"entry_hash": "8a2f...c1e9",
"ts": 1746478291000,
"event_type": "decision.made",
"event_canonical": "{\"agent_id\":\"acme-bot-1\",...}"
},
"sth": {
"tree_size": 500,
"root_hash": "1f9c...7e2a",
"signed_at": 1746478320000,
"signer_kid": "foundation:root-1",
"signature": "BASE64...",
"rekor_log_id": "...",
"rekor_index": 12345678,
"rekor_uuid": "abcd..."
},
"proof": ["...", "...", "..."],
"leaf_hash": "f8e1...0a7d",
"rekor_url": "https://rekor.sigstore.dev/api/v1/log/entries/abcd..."
}
],
"directive_audit": {
"directive_id": "dir-3a91b...c4e",
"applied_at": 1746391891000,
"expired_at": 1746478291000,
"agents": ["acme-bot-1", "acme-bot-7"],
"audit_chain_entries": [
{
"agent_id": "acme-bot-1",
"seq": 311,
"entry": { "event_type": "directive.applied", "ts": 1746391891000, "..." },
"sth": { "tree_size": 500, "root_hash": "1f9c...7e2a", "..." },
"proof": ["...", "..."],
"leaf_hash": "...",
"rekor_url": "https://rekor.sigstore.dev/api/v1/log/entries/..."
}
]
}
}
How the regulator verifies — locally
The regulator does not need to trust Iqrar's response. Every proof is verifiable against the public Rekor log.
import { verifyInclusionProof, leafHash } from "@iqrar/rules/merkle";
for (const item of response.proofs) {
// 1. Recompute the leaf hash from the entry's entry_hash.
const expected = await leafHash(item.entry.entry_hash);
if (expected !== item.leaf_hash) throw new Error("leaf mismatch");
// 2. Verify the inclusion proof reproduces the STH root.
const ok = await verifyInclusionProof(
item.leaf_hash,
item.seq,
item.sth.tree_size,
item.proof,
item.sth.root_hash,
);
if (!ok) throw new Error("inclusion proof invalid");
// 3. Confirm the STH was actually published to Rekor.
const rekorEntry = await fetch(item.rekor_url).then((r) => r.json());
// Verify the Rekor entry's body matches our STH root + signer key.
// (See @iqrar/rules/rekor for the canonical verification helper.)
}
If any step fails, the operator's claim that the agent recorded a particular action at a particular time is not credible — even if Iqrar's response was 200 OK.
The directive↔challenge binding
When a challenge carries bound_directive_id, the response's directive_audit section contains inclusion proofs for the directive's own lifecycle audit-chain entries — directive.received, directive.applied, directive.skipped, directive.rejected — across every targeted agent.
This closes the §8.3 sub-claim: the investigative chain is itself committed and verifiable. A tamper that excised a directive-application record from a regulator's view of the chain would invalidate the inclusion proof against the same STH that anchors the action records.
The regulator can therefore verify, in a single pass:
- The directive was issued.
- Every targeted agent received it.
- The agents that matched applied it (with the recorded window).
- The action records produced under the directive's window are committed under the same STHs.
- Nothing has been retroactively altered.
That property is the regulator's primary audit guarantee.
Capability gate
To issue an audit challenge, an authority must have may_issue: ["audit_challenge"] in the registry. By convention, rule_bundle and directive are the publication capabilities; audit_challenge is the inquiry capability. The same authority typically holds all three — but the registry can grant them separately, e.g., for a regulator that delegates inquiry to a dedicated supervisory unit.
See also
- Concepts overview — what the audit chain, Merkle commitment, and Rekor anchoring do —
- Foundation — why the registry's signing keys are governed by a structurally separate entity —
- Sigstore Rekor — the public transparency log Iqrar anchors STHs to — rekor.sigstore.dev