Protocol Specification
The Veil Protocol
A Veil Certificate is a signed, timestamped, externally anchored attestation that a single AI inference request kept identity data and AI processing isolated throughout the pipeline. This page describes the protocol: what the certificate contains, how it is produced, the five consistency checks the Witness runs, and how an obtained certificate is verified end-to-end against public standards without trusting our code.
Last Updated: April 2026
The Veil Protocol covers isolation attestation— per-request proof that the split-knowledge boundary held. The evidence layer covers decision provenance— per-decision records of what the AI output and why. Both surfaces use the same cryptographic primitives (Ed25519, SHA-256 hash chains, RFC 3161, Sigstore Rekor), but they attest to different things. This page is the protocol reference; the evidence layer has its own page.
What a Veil Certificate Is
For every inference request that passes through the Gateway, the protocol produces a signed envelope containing: a fail-closed Gateway claim, best-effort claims from the four downstream pipeline services (dsa-bridge, dsa-sanitizer, dsa-ai, dsa-audit), the Witness verdict over five consistency checks, an RFC 3161 timestamp token, and — on a best-effort async retry path — a Sigstore Rekor entry reference.
The certificate is retrievable at three gateway endpoints, one per view: /api/v1/veil/certificate/{request_id} (full envelope, technical-proof view), /api/v1/veil/certificate/{request_id}/summary (DPO summary), and /api/v1/veil/certificate/{request_id}/regulatory (regulatory-mapping view). It is not a credential and not a license. It is an attestation about one request, independently verifiable against external trust anchors.
Attestation scope
Attests:
- +The Gateway recorded an Ed25519-signed, request-level Veil claim.
- +The Witness ran its five consistency checks against the claim set.
- +The envelope has not been altered since Witness signing.
- +When anchor_status == ANCHORED: the envelope existed no later than the RFC 3161 timestamp, and the envelope hash appears in the Sigstore Rekor transparency log.
Does not attest:
- −What the AI output was — that is the evidence layer's scope.
- −The legal correctness of your use case.
- −The absence of bugs in your own application code downstream of the Gateway.
The Five Consistency Checks
A Veil Certificate carries two label axes the Witness computes independently:
completenessFULLif all four expected downstream services (dsa-bridge,dsa-sanitizer,dsa-ai,dsa-audit) emitted a claim;PARTIALif any are missing. The Gateway’s own claim is guaranteed by a separate fail-closed invariant at the edge — if the Gateway cannot record it, the request is refused before any certificate exists.overall_verdictThe Witness’s combined verdict over all checks:VERIFIEDif everything passes on aFULLclaim set;PARTIALif signatures, data-visibility, and isolation-probe all pass but either completeness isPARTIALor a temporal check fails;FAILEDif signatures are invalid, data visibility is violated, or the isolation probe is notVERIFIED.FAILEDcertificates are persisted and returned with that verdict — they are not withheld.
The five checks the Witness runs, in code order:
- 1
Signature validity
Every claim’s Ed25519 signature must verify against that service’s published public key. In addition, the service’s typed proto fields (
IsolationProbe,QiScore,ModelUsed, etc.) are cross-checked against the signed canonical payload — a mismatch means the outer fields were tampered with after signing. Any mismatch is fatal →FAILED. - 2
Claim completeness
All four expected downstream services must be present to earn
completeness = FULL; otherwisePARTIAL, with the missing services listed. Incomplete on its own only degradesoverall_verdicttoPARTIAL— unless the missing service isdsa-ai, in which case the isolation-probe check has no claim to consume andoverall_verdictfalls toFAILED. - 3
Temporal consistency
Claim timestamps must fall within a 120-second skew window and arrive in the expected pipeline order (
dsa-bridge→dsa-sanitizer→dsa-ai→dsa-audit). Either sub-check failing degradesoverall_verdicttoPARTIAL. - 4
Data visibility
Sandbox B (
dsa-ai) must not claim to have seen any PII field name matching the pattern list (customer_id,email,name,first_name,last_name,phone,address,ssn,dob,date_of_birth,identity_id). A violation is fatal →FAILED. - 5
Isolation probe
Sandbox B’s claim carries an
IsolationProbestatus. OnlyVERIFIEDpasses;BREACHED,LOCKED, andUNKNOWNall fail this check →FAILED.
The Signing Process
Each certificate is produced through a four-stage pipeline. The first two stages run synchronously on the inference path; the external-anchoring stage runs asynchronously after Witness signing. Every stage is independently verifiable.
- 1
Per-service Ed25519 claim
Each pipeline service holds a per-service Ed25519 keypair. When it processes a request, it emits a claim: its service identifier, the request identifier, a structured summary of what data it saw, and its timestamp. It signs the claim with its private key, producing a canonical payload binding to the typed proto fields. Keys are managed through the deployment’s secret manager (HashiCorp Vault, AWS KMS, or Azure Key Vault).
- 2
Witness verification and envelope signature
The Witness collects claims emitted during the request, runs the five consistency checks, and composes the certificate envelope: all claims, the verdict axes (
completeness,overall_verdict), the missing-service list (if any), and the signing key identifier. The envelope is signed with the Witness’s Ed25519 key; this signature is the certificate’s primary trust anchor. - 3
External anchoring (RFC 3161 TSA + Sigstore Rekor, async best-effort)
After the Witness signs the envelope, an attestor submits a hash of the certificate to two external services. The input artifact for both anchors is the same protobuf-binary serialization of the
VeilCertificateproto as it exists at Witness signing time — before the attestor populates three specific fields:attestation.timestamp,attestation.transparency_log, and the top-levelanchor_status. Note thatattestation.notaryis different: the assembler populates it at signing time with the Witness’s canonical-JSON signature re-asserted as metadata, so it is part of the hashed bytes. A verifier reconstructing the hash must therefore clear exactly those three fields (attestation.timestamp,attestation.transparency_log,anchor_status) on the fetched certificate beforeproto.Marshal, and preserveattestation.notary. The attestor sends this hash to an external Timestamp Authority per RFC 3161 (producing a signed timestamp token proving the envelope existed no later than the token’sgenTime) and to Sigstore Rekor as ahashedrekordentry. Both submissions run on the same async retry path; the result is surfaced asanchor_status:PENDING_ANCHORwhile retries are in flight,ANCHOREDwhen both the TSA token and a Rekor inclusion proof are populated on the persisted certificate, andANCHOR_FAILEDwhen the retry window is exhausted without full confirmation. A cert withANCHOR_FAILEDremains valid against its Witness Ed25519 signature; only the external public anchors did not confirm. - 4
Key lifecycle
Per-service Ed25519 keys rotate on an operator-configured schedule. Claims do not record a
key_id; theVeilClaimproto carries only the signing service’sservice_idand itssignature. A verifier looks up the service’s current public key byservice_idfrom the key manifest described below. The certificate envelope separately recordswitness_key_id— that identifier applies to the Witness envelope signature only. Rotation is independent per service, so a key compromise is scoped. Because claims do not pin a specific key version, a verifier checking a historical certificate relies on the manifest to publish the public key that was current for that service at the claim’s timestamp (or on retained historical keys if the operator has rotated since). Operators who rotate keys must serve a manifest that exposes the historical public key alongside the current one; otherwise, certificates signed under the retired key become unverifiable. An operator who cannot retain historical keys should retire certificates signed under the old key from verification flows before rotation.
The gateway publishes the key manifest atGET /.well-known/veil-keys.json— public, unauthenticated. The manifest returns{ issuer, version, supported_protocol_versions, keys[], signed_at }, where each entry inkeys[]is{ service_id, key_id, public_key, purpose, algorithm: "Ed25519" }. When the gateway is configured with a manifest signing key (VEIL_MANIFEST_SIGNING_KEYenv var), the manifest is Ed25519-signed over its canonical JSON form and ships withsignature,signing_key_id, andsigning_algorithmattached. When that env var is absent, the manifest ships unsigned — consumers who require manifest integrity should pin a signed mirror (typically in their DPA appendix) and compare before trusting it.
The Three Views
Every certificate is retrievable in three views. The underlying signed envelope is identical; the views differ only in what they render. Each view is a deterministic render of the same canonical JSON — the render itself is verifiable against the signed envelope.
| View | Audience | Renders |
|---|---|---|
| DPO summary | Data Protection Officer | Plain-language: what was separated, what the AI saw, what it did not, and the regulatory mapping. No cryptographic detail. |
| Technical proof | Security engineer | Full envelope: every claim with its Ed25519 signature, the TSA token, the Rekor entry reference (when ANCHORED), key identifiers, and canonical hashes. |
| Regulatory mapping | Auditor | Article-by-article: which claim supports GDPR Art. 25/32 and EU AI Act Art. 10/15. Evidence mapping, not a held certification. |
How to Verify a Veil Certificate Independently
A Veil Certificate is designed so that verification of an obtained certificate does not require trusting our code. The certificate fetch does require an authenticated call against the tier-gated /api/v1/veil/certificate/{request_id} endpoint (x-api-key, Pro or Enterprise tier) — that’s our transport. Once fetched, the certificate can be verified end-to-end against public standards (Ed25519, RFC 3161, Sigstore Rekor) and against keys we publish at /.well-known/veil-keys.json (unauthenticated). Two paths exist: a convenience path via the TypeScript SDK, and a protocol-agnostic path using standard tooling. Honest picture: certificate fetch and Witness-signature verification work end-to-end today; full multi-claim verification via an independent Go oracle is on the roadmap. Where current tooling stops short, we say so.
Convenience path (TypeScript SDK)
The @dsaveil/theveilSDK ships a certificate fetch and a Witness-signature verification helper. The verifier does not trust the certificate to self-identify its signer — the caller supplies the expected Witness key out-of-band.
import { TheVeil, TheVeilCertificateError, TheVeilHttpError } from "@dsaveil/theveil";
const client = new TheVeil({ apiKey: process.env.VEIL_API_KEY });
// 1) Fetch. A cert that is not yet assembled surfaces as a 202 HTTP error
// so the happy-path return type stays a narrow VeilCertificate.
let cert;
try {
cert = await client.getCertificate("req_4f3a1b2c8d9e");
} catch (err) {
if (err instanceof TheVeilHttpError && err.status === 202) {
// retry after err.body.retry_after_seconds
}
throw err;
}
// 2) Verify the Witness Ed25519 signature over the certificate's
// canonical 7-field subset. Throws TheVeilCertificateError on
// failure — you do not check a boolean return.
try {
const result = await client.verifyCertificate(cert, {
witnessKeyId: "witness_v1", // expected label
witnessPublicKey: process.env.VEIL_WITNESS_PUBKEY!, // base64 or Uint8Array (32 bytes)
});
// result.overallVerdict — VERDICT_UNSPECIFIED | VERDICT_VERIFIED | VERDICT_PARTIAL | VERDICT_FAILED
// result.anchorStatus — ANCHOR_STATUS_UNSPECIFIED | ANCHOR_STATUS_PENDING | ANCHOR_STATUS_ANCHORED | ANCHOR_STATUS_FAILED
// result.witnessAssertedIssuedAt — Date (millisecond precision)
if (result.overallVerdict === "VERDICT_VERIFIED") {
// all five Witness checks passed, complete claim set
}
} catch (err) {
if (err instanceof TheVeilCertificateError) {
// err.reason is one of:
// malformed | unsupported_protocol_version | witness_mismatch
// witness_signature_missing | invalid_signature
}
throw err;
}verifyCertificate today performs four checks: protocol version (protocol_version === 2), witness identity (cert.witness_key_idmatches the caller-supplied expected label), signature presence, and Ed25519 verification over the canonical 7-field subset. External RFC 3161 timestamp verification and Rekor inclusion-proof verification are notperformed by the SDK today — anchorStatus and overallVerdict on the result are pass-through metadata. Full multi-claim verification (each per-service Ed25519 signature, the TSA token chain, the Rekor inclusion proof) is the roadmap step; when shipped, it will run inside the SDK without additional caller configuration.
A note on enum forms. Your code compares against the protojson full-name literals shown above: VERDICT_VERIFIED, VERDICT_PARTIAL, VERDICT_FAILED, ANCHOR_STATUS_ANCHORED, and so on. These are what the SDK returns, because the gateway serves the certificate as protojson with UseProtoNames: true. Elsewhere on this page we write the shorter forms — VERIFIED, PARTIAL, ANCHORED— when narrating protocol behaviour, because those are the strings the Go Witness uses internally and canonically signs. Same values, two encodings. In your code, always compare to the full-name form.
Protocol-agnostic path (standard tooling)
Five explicit steps. No DSA-specific dependencies.
- 1
Fetch the envelope
curlthe certificate URL from the inference response’sveil.certificate_urlfield, or the/api/v1/veil/certificate/{request_id}endpoint directly (authenticated:x-api-keyheader, Pro or Enterprise tier). The response body isprotojsonoutput of theVeilCertificateproto — JSON, but with protojson’s convention that enum fields carry full-name string values (e.g.anchor_status.status=ANCHOR_STATUS_ANCHORED,verification.overall_verdict=VERDICT_VERIFIED) and unset fields are emitted with zero values. Parse as JSON for inspection, then — for signature and hash reconstruction — re-encode as protobuf binary using the proto schema atproto/veil/v1/veil.protoin the public DSA repo. - 2
Verify the Witness envelope signature
Extract the envelope body and its Ed25519 signature. Obtain the Witness public key from the gateway’s key manifest:
GET /.well-known/veil-keys.json(public, unauthenticated). Find the entry whereservice_id == "dsa-witness"andkey_idmatches the certificate’switness_key_id(today the manifest exposeswitness_v1for this role). If the gateway is configured with a manifest signing key, verify the manifest’s own Ed25519 signature first; otherwise pin a signed manifest mirror from the operator’s DPA appendix before trusting it. The signed payload is the certificate’s canonical 7-field subset (certificate_id,request_id,protocol_version,claim_idsin order,issued_at,overall_verdictin Witness short form (VERIFIED/PARTIAL/FAILED), andwitness_key_id) — see the Witness assembler for the exact canonicalisation. Verify with any Ed25519 library (RFC 8032) —openssl pkeyutl -verify, a PyNaCl call, or equivalent. - 3
Verify per-service claim signatures
The envelope contains one claim per pipeline service that emitted one. For each, repeat step 2 against that service’s published key — looked up by the claim’s
service_idfrom the same/.well-known/veil-keys.jsonmanifest (today one currentkey_idperservice_id). TheVeilClaimproto does not carry akey_idfield: claims expose onlyservice_idandsignature, so rotation handling lives in the manifest (see the Key Lifecycle subsection of Section 3). Missing best-effort claims amongdsa-bridge/dsa-sanitizer/dsa-ai/dsa-auditare normal onPARTIALcertificates; a missing Gateway claim is not possible (the request would have been refused). - 4
Verify the RFC 3161 timestamp token (only when anchor_status == ANCHORED)
When
anchor_status == ANCHORED, the envelope’sattestation.timestampfield carries the TSA token (raw bytes atattestation.timestamp.timestamp_token). Verify the token’s signature against the TSA’s published CA chain, then verify that the token’smessageImprint.hashedMessageequalsSHA-256(proto.Marshal(cert)), wherecertis the fetchedVeilCertificatewith these three fields cleared to their unset state:attestation.timestamp,attestation.transparency_log, and the top-levelanchor_status. Preserveattestation.notary— it was populated at signing time and is part of the hashed bytes (clearing it produces a hash mismatch on a legitimate certificate). ThemessageImprint.hashAlgorithmmust beid-sha256(OID2.16.840.1.101.3.4.2.1). Tools:openssl ts -verifypointed at the reconstructed bytes via-data, or any TSP library. OnPENDING_ANCHORorANCHOR_FAILED, skip this step — theattestation.timestampfield may be absent or incomplete; the certificate is still valid against the Witness Ed25519 signature, but external timestamp anchoring did not confirm. - 5
Verify the Rekor entry (only when anchor_status == ANCHORED)
When
anchor_status == ANCHORED, the envelope’sattestation.transparency_logfield contains the Rekor entry:log_index(int64),inclusion_proof(bytes),signed_entry_timestamp(bytes), andlog_url(the Rekor endpoint that issued the proof). Fetch the entry via the Rekor REST API orrekor-cli. The entry is ahashedrekordwithdata.hash.algorithm == "sha512"— confirmdata.hash.valueequalshex(SHA-512(proto.Marshal(cert))), wherecertis the fetchedVeilCertificatewith these three fields cleared to their unset state:attestation.timestamp,attestation.transparency_log, and the top-levelanchor_status. Preserveattestation.notary— it was populated at signing time and is part of the hashed bytes (clearing it produces a hash mismatch on a legitimate certificate). Note that the entry’s signature is Ed25519ph(pre-hash; RFC 8032 §5.1), notthe same construction as the Witness envelope signature — Rekor verifies it internally against the public key embedded in the entry’sspec.signature.publicKey.content(PEM-wrapped X.509 SPKI of the Witness public key). OnPENDING_ANCHORorANCHOR_FAILED, skip this step — the transparency-log field may be absent or incomplete.# The log_index is a field of attestation.transparency_log in the fetched cert. rekor-cli get --log-index <attestation.transparency_log.log_index>
The five steps above are a complete manual verification against the protocol. They do not require trusting our infrastructure or our code — every artifact is checked against public standards (Ed25519, RFC 3161, Sigstore Rekor) or against keys we publish at /.well-known/veil-keys.json (or pinned signed mirrors thereof).
Regulatory Mapping
The Veil Protocol is one mechanism a processor can use to meet specific articles of GDPR and the EU AI Act. These are evidence and architectural mappings — not a held certification under either regulation.
| Regulation | Requirement | What the Veil Certificate contributes |
|---|---|---|
| GDPR Art. 25 | Data protection by design and by default | Per-request evidence that pseudonymisation was the default processing mode and that identity fields did not cross into the AI environment. |
| GDPR Art. 32 | Security of processing | Cryptographic attestation signed at the infrastructure layer. The signature chain is tamper-evident; external TSA timestamps and Rekor anchoring make tampering detectable across custody boundaries. Independent verification of an obtained certificate does not require the processor's cooperation (verification uses only public standards and the unauthenticated /.well-known/veil-keys.json manifest). Certificate fetch requires the customer's own authenticated call to a tier-gated gateway endpoint. |
| EU AI Act Art. 10 | Data governance for high-risk AI | The isolation-probe claim demonstrates the AI system did not receive identity data for the specific request. The certificate is a per-request, inspectable governance artifact. |
| EU AI Act Art. 15 | Accuracy, robustness, and cybersecurity | The certificate is an externally-anchored robustness signal: any alteration of the envelope after signing is detectable. Independent verification of an obtained certificate uses only public standards and the unauthenticated /.well-known/veil-keys.json manifest; certificate fetch requires the customer's own authenticated call to the tier-gated gateway endpoint. |
What the Protocol Does Not Do
The Veil Protocol proves isolation happened for a given request. It does not, by itself, prove that the AI’s output was correct, that your application interpreted the output safely, or that your own systems downstream of the Gateway are equally isolated. Infrastructure enforcement (Kubernetes NetworkPolicies, Docker network segmentation) is what enforces the isolation; the protocol is what proves it. Neither replaces the other.
FAQ
- What happens if anchor_status is stuck on PENDING_ANCHOR?
PENDING_ANCHORmeans the async retry loop has not yet populated both external attestations (the RFC 3161 TSA timestamp and the Sigstore Rekor inclusion proof) on the persisted certificate. The Witness Ed25519 signature over the envelope is produced synchronously and is already valid; both external anchors are the async part and are whatPENDING_ANCHORis waiting on. If the status does not transition toANCHOREDwithin the retry window, it moves toANCHOR_FAILED; an auditor who requires a transparency-log anchor for a specific certificate can retry the fetch or request a fresh run. Long-runningPENDING_ANCHORbeyond the configured window is an operational signal that the attestor service cannot reach the external TSA or Rekor endpoints — check the deployment’s attestor logs and anchor retry metrics.- How do I verify a certificate after my API key has been rotated or revoked?
- Your API key is unrelated to certificate verification. Certificates are signed with the Witness service’sEd25519 key and cross-anchored by the RFC 3161 TSA and Sigstore Rekor. The envelope records
witness_key_idand is signed by the Witness Ed25519 key. Per-service claims carry onlyservice_idandsignature— there is no claim-levelkey_idfield. The public key used to verify a given claim is looked up byservice_idfrom the/.well-known/veil-keys.jsonmanifest. Operators who rotate service keys must publish the retired public keys in the manifest alongside the current ones, otherwise claims signed before the rotation become unverifiable. API-key revocation prevents new inference requests under that API key but has no effect on certificates already produced. - Can I replay an old certificate to prove something today?
- A Veil Certificate is bound to a specific
request_id, claim chain, and TSA timestamp. Its Ed25519 signatures cover that exact envelope — presenting the same certificate as proof of a different request or a current decision fails on therequest_idbinding alone. What the certificate does continue to prove, indefinitely, is that the original request went through the isolated pipeline at the time recorded in the TSA token. Replay against the original request is a feature; replay against anything else is not a threat the protocol tries to prevent, because the binding makes it mechanically impossible.