Skip to content

Test Vectors

Spec version: 0.10.2 · Generated: 2026-06-12 · Live vectors valid until: 2027-06-12

Machine-readable conformance vectors for KTC senders and receivers. Every vector is a JSON file pairing an input (a SHLink, in bare and viewer-prefixed forms, plus a QR image) with expected outcomes — the decoded payload, the SHA-256 of the decrypted Bundle, or the pipeline stage at which a conformant receiver rejects.

All content is synthetic (Jessica Argonaut). Keys are published on purpose: the decryption key travels inside every SHLink anyway, and these links protect nothing.

Three ways to use them

1. Live. Payload urls point at static files on this site, which serves Access-Control-Allow-Origin: * — so the links resolve from any receiver, including browser-based ones, with no test server. (Static hosting can't enforce the recipient parameter or exercise dynamic revocation/use counting — test those against a reference implementation.)

2. Offline, in CI. Each vector embeds a responses map (URL → status, headers, body file), so a harness can shim fetch and run with no network:

js
import { makeFetchShim, runVector } from './harness.mjs' // served at /vectors/harness.mjs

const index = JSON.parse(await readFile('vectors/index.json', 'utf8'))
const vectors = await Promise.all(index.vectors.map(v => readJson(`vectors/${v.file}`)))
const fetchFn = makeFetchShim(vectors, f => readFile(`vectors/${f}`, 'utf8'))
for (const v of vectors) console.log(await runVector(v, { fetchFn }))

Download everything: index.json · harness.mjs — or swap runVector's reference pipeline for calls into your own implementation and assert against expect.

3. By hand. Point your scanner at the QR wall below, or paste any SHLink — one of these, or one your own app produced — into the debugger.

Catalog

Tier 1 — Decode

Pure functions: SHLink carrier forms and payload decoding. No network needed.

IDTitleLevelExpectedSpecFiles
ktc-d1-baselineBare SHLink, baseline
Canonical bare shlink:/ URI. U flag, exp, label, uncompressed JWE, minimal Bundle (Patient + Patient Story PDF).
baselineresolves#shlink-payload-decoded
vector · bundle · QR
ktc-d2-prefixedViewer-prefixed SHLink
The same link conveyed as https://viewer.example/#shlink:/… — the form patients paste in the ahead-of-time check-in workflow. Receivers extract the shlink:/ substring. This vector's QR encodes the PREFIXED form.
baselineresolves#shlink-constraints
#workflow
vector · bundle · QR
ktc-d3-b64urlbase64url alphabet (the atob trap)
Payload whose base64url encoding contains both '-' and '_'. Decoders using plain base64/atob() fail here; correct base64url decoding succeeds. Observed failure in a real scanner.
baselineresolves#shlink-payload-decoded
vector · bundle · QR
ktc-d4-utf8-labelUTF-8 label
Label contains multi-byte UTF-8 (em dash, accents). Decoders that treat decoded bytes as latin1 show mojibake; the expected label string is published.
baselineresolves#shlink-payload-decoded
vector · bundle · QR
ktc-d5-no-labelNo label
label is 0..1 — absent here. Receivers must not require it.
baselineresolves#shlink-payload-decoded
vector · bundle · QR
ktc-d6-no-expMissing exp (valid base SHL, not KTC)
exp is required by this IG (1..1). A payload without it is a conformant base-spec SHLink but NOT a KTC link; receivers validating KTC conformance flag it.
negativereject @ payload#shlink-payload-decoded
vector
ktc-d7-flag-pFlag P (out of KTC scope)
KTC requires flag exactly "U". P (passcode + manifest flow) is valid base SHL but out of scope here.
negativereject @ payload#shlink-payload-decoded
vector
ktc-d8-flag-emptyEmpty flag (manifest flow, out of KTC scope)
No U flag means the URL is a manifest endpoint (POST), not a direct file. Out of KTC scope; receivers must not GET it as if it were U-flagged.
negativereject @ payload#shlink-payload-decoded
vector
ktc-d9-truncatedTruncated payload
Garbage in: the base64url JSON is cut off. Decoders must reject without crashing.
negativereject @ decode#shlink-payload-decoded
vector

Tier 2 — Retrieve

HTTP behavior: missing files, expired links.

IDTitleLevelExpectedSpecFiles
ktc-r1-goneGone: payload URL returns 404
A well-formed, unexpired link whose file is gone (revoked / exhausted). Receivers show a clear, friendly error — never a crash, never partial data.
robustnessreject @ retrieve#retrieval-protocol
vector · QR
ktc-r2-expiredExpired link
exp is in the past (pinned constant). Receivers SHOULD display an expiration error when scanned — ideally before fetching at all. The file is still hosted, so behavior past the exp check is observable.
robustnessreject @ payload#expiration
vector · QR

Tier 3 — Decrypt

JWE handling: compression, tampering, key alphabet.

IDTitleLevelExpectedSpecFiles
ktc-e1-zip-defJWE with zip: DEF
The base SHL spec permits zip: DEF (raw RFC 1951 deflate before encryption); this IG does not rule it out. Receivers should inflate when the header declares it. Observed failure in a real scanner.
robustnessresolves#decryption
vector · bundle · QR
ktc-e2-tamperedTampered ciphertext
One ciphertext character flipped. AES-GCM authentication MUST fail; the receiver must fail closed and never render partially-decrypted output.
negativereject @ decrypt#decryption
#security-considerations
vector · QR
ktc-e3-key-urlsafeKey with base64url-only characters
The 43-char key contains '-' and '_'. Decoders converting via plain base64 corrupt the key and fail the auth tag.
baselineresolves#decryption
vector · bundle · QR

Tier 4 — Bundle

PatientSharedBundle profile, persistence floor, App Attestation.

IDTitleLevelExpectedSpecFiles
ktc-b1-discrete-onlyDiscrete resources only (no DocumentReference)
Apps MAY share only discrete FHIR resources. No PDF anywhere; receivers must not require one.
baselineresolves#fhir-bundle-profile-patientsharedbundle
vector · bundle · QR
ktc-b2-both-pdfsBoth PDF kinds in one Bundle
FHIR-Rendered PDF (60591-5) and Patient Story PDF (51855-5) together. Receivers SHALL distinguish them by type and treat them as distinct documents.
baselineresolves#documentreference-profile-patientshareddocumentreference
vector · bundle · QR
ktc-b3-persistence-coreThe SHALL-persist four
US Core-shaped Condition, MedicationRequest, AllergyIntolerance, and Immunization plus a FHIR-Rendered PDF. A conformant receiver persists all five chart-associated.
baselineresolves#conformance-requirements
vector · bundle · QR
ktc-b4-carin-dicCARIN DIC Coverage
Coverage per CARIN Digital Insurance Card — an in-scope MAY for senders; receivers SHOULD persist it as part of full-USCDI content.
baselineresolves#fhir-bundle-profile-patientsharedbundle
vector · bundle · QR
ktc-b9-carried-noteOrdinary DocumentReference carried along
A US Core clinical-note DocumentReference from the patient's record (LOINC 11506-3, text/rtf, no patient-shared category) alongside the KTC PDFs. The PatientSharedDocumentReference profile constrains only the two patient-shared kinds; other DocumentReferences are ordinary USCDI content and MUST be accepted.
baselineresolves#documentreference-profile-patientshareddocumentreference
vector · bundle · QR
ktc-b5-attestationApp Attestation (valid)
Bundle.meta.extension carries an ES256 JWS. iss = https://ktc-spec.github.io/vectors/attestation; JWKS at {iss}/.well-known/jwks.json (hosted by this site). Verification should succeed; the key is an intentionally public test key.
baselineresolves#app-attestation-optional
vector · bundle · QR
ktc-b6-attestation-brokenApp Attestation (unverifiable)
The attestation signature fails verification. Receivers SHALL NOT block Bundle processing — show no provenance indicator and continue.
robustnessresolves#app-attestation-optional
vector · bundle · QR
ktc-b7-bad-typeBundle.type is document (invalid)
type must be collection. Receivers reject or flag — and never crash.
negativereject @ bundle#fhir-bundle-profile-patientsharedbundle
vector · bundle · QR
ktc-b8-single-entryPatient-only Bundle (invalid)
entry is 2..* — a Patient with no content entry violates the profile.
negativereject @ bundle#fhir-bundle-profile-patientsharedbundle
vector · bundle · QR

QR Wall

Point a real scanner at the screen. Each QR encodes the bare shlink:/ URI (the prefixed-form vector encodes the https://…#shlink:/… URL instead). Compare what your implementation shows against the expectation.

QR for ktc-d1-baseline
ktc-d1-baseline

You should see: Jessica Argonaut's health summary — Jessica Argonaut, DOB 1985-03-15, 2 entries.

QR for ktc-d2-prefixed
ktc-d2-prefixed

You should see: Prefixed form — same payload either way — Jessica Argonaut, DOB 1985-03-15, 2 entries.

QR for ktc-d3-b64url
ktc-d3-b64url

You should see: 🧪x🔗📦 sonde numéro 1 — Jessica Argonaut, DOB 1985-03-15, 2 entries.

QR for ktc-d4-utf8-label
ktc-d4-utf8-label

You should see: Jessica Argonaut — résumé de santé — Jessica Argonaut, DOB 1985-03-15, 2 entries.

QR for ktc-d5-no-label
ktc-d5-no-label

You should see: (no label) — Jessica Argonaut, DOB 1985-03-15, 2 entries.

QR for ktc-r1-gone
ktc-r1-gone

Expected: clear error at the retrieve stage.

QR for ktc-r2-expired
ktc-r2-expired

Expected: clear error at the payload stage.

QR for ktc-e1-zip-def
ktc-e1-zip-def

You should see: Compressed payload — Jessica Argonaut, DOB 1985-03-15, 6 entries.

QR for ktc-e2-tampered
ktc-e2-tampered

Expected: clear error at the decrypt stage.

QR for ktc-e3-key-urlsafe
ktc-e3-key-urlsafe

You should see: URL-safe key alphabet — Jessica Argonaut, DOB 1985-03-15, 2 entries.

QR for ktc-b1-discrete-only
ktc-b1-discrete-only

You should see: Discrete FHIR only — Jessica Argonaut, DOB 1985-03-15, 5 entries.

QR for ktc-b2-both-pdfs
ktc-b2-both-pdfs

You should see: Story + FHIR-Rendered — Jessica Argonaut, DOB 1985-03-15, 7 entries.

QR for ktc-b3-persistence-core
ktc-b3-persistence-core

You should see: Persistence floor — Jessica Argonaut, DOB 1985-03-15, 6 entries.

QR for ktc-b4-carin-dic
ktc-b4-carin-dic

You should see: Insurance card — Jessica Argonaut, DOB 1985-03-15, 3 entries.

QR for ktc-b9-carried-note
ktc-b9-carried-note

You should see: Record note rides along — Jessica Argonaut, DOB 1985-03-15, 7 entries.

QR for ktc-b5-attestation
ktc-b5-attestation

You should see: Attested bundle — Jessica Argonaut, DOB 1985-03-15, 2 entries.

QR for ktc-b6-attestation-broken
ktc-b6-attestation-broken

You should see: Broken attestation — must not block — Jessica Argonaut, DOB 1985-03-15, 2 entries.

QR for ktc-b7-bad-type
ktc-b7-bad-type

Expected: clear error at the bundle stage.

QR for ktc-b8-single-entry
ktc-b8-single-entry

Expected: clear error at the bundle stage.

Paste any SHLink — bare or viewer-prefixed, a vector from this page or one produced by your own Patient App — and watch each pipeline stage pass or fail. Everything runs in your browser (decode, fetch with recipient=KTC Spec Debugger, WebCrypto decrypt incl. zip: DEF, profile checks); nothing is sent to any server other than the link's own payload URL.

Draft specification maintained on GitHub.