From 313222e6ce6e8e7f81d4f3b80f0425b6ef427517 Mon Sep 17 00:00:00 2001 From: applesnort Date: Tue, 30 Jun 2026 16:33:44 -0400 Subject: [PATCH] feat(vertical): add government IDs and licensing vertical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new /government/ vertical covering driver's licenses, professional licenses, and age verification with VCALM — with landing, role picker, and issuer/verifier/wallet build guides. Includes three new flow icons (governmentIssue/Hold/Disclose). --- src/_data/flowIcons.js | 53 +++++ src/verticals/government/build/index.njk | 39 ++++ src/verticals/government/build/issuer.njk | 167 +++++++++++++++ src/verticals/government/build/verifier.njk | 190 +++++++++++++++++ src/verticals/government/build/wallet.njk | 79 +++++++ src/verticals/government/government.json | 4 + src/verticals/government/index.njk | 215 ++++++++++++++++++++ 7 files changed, 747 insertions(+) create mode 100644 src/verticals/government/build/index.njk create mode 100644 src/verticals/government/build/issuer.njk create mode 100644 src/verticals/government/build/verifier.njk create mode 100644 src/verticals/government/build/wallet.njk create mode 100644 src/verticals/government/government.json create mode 100644 src/verticals/government/index.njk diff --git a/src/_data/flowIcons.js b/src/_data/flowIcons.js index e23b842..d602f92 100644 --- a/src/_data/flowIcons.js +++ b/src/_data/flowIcons.js @@ -132,6 +132,59 @@ export default { ✓ verified `, + governmentIssue: ``, + + governmentHold: ``, + + governmentDisclose: ``, + // Small heading glyphs for the "What X experiences" columns. // Individual holder (resident / graduate / buyer). person: `

What are you building?

+

Each role sets your scope before you see any code.

+ + + +

You do NOT need to implement the other two.

+ diff --git a/src/verticals/government/build/issuer.njk b/src/verticals/government/build/issuer.njk new file mode 100644 index 0000000..3fbec88 --- /dev/null +++ b/src/verticals/government/build/issuer.njk @@ -0,0 +1,167 @@ +--- +title: "Build an issuer (agency) — government IDs & licensing" +description: "Issue a government credential as a VCALM coordinator: create an exchange, share an interaction URL, poll for the result. A workflow service handles the on-the-wire protocol." +permalink: /government/build/issuer/ +--- +{# Layer 4 — issuer coordinator. A coordinator is a CLIENT of a workflow + service; it does not run @bedrock/vc-delivery itself. It only creates + exchanges, hosts/shares interaction URLs, and polls for results. #} +
+

Build an issuer

+

+ You run the agency side — a DMV, a licensing board, or a state agency. As a + coordinator you speak VCALM — just the coordinator side of + it. You don't run VCALM delivery, the on-the-wire exchange with the + resident's wallet; a workflow service does that. Your whole job is + three calls: create an exchange, share an interaction URL that points the + resident's wallet at it, then poll the exchange until the wallet has been + issued the credential. +

+ + + +

What the coordinator does

+
    +
  1. Create an exchange by POSTing the workflow's variables + to its exchanges endpoint.
  2. +
  3. Host and share an interaction URL that points the + resident's wallet at that exchange.
  4. +
  5. Poll the exchange until it completes, then read the + result.
  6. +
+

+ The workflow service does everything in between — protocol negotiation, + DID authentication, signing the credential, talking to whatever wallet shows + up — and hands you back just the result. +

+ +

1. Create the exchange

+

+ POST to your workflow's exchanges endpoint. The body carries a + ttl and the variables the workflow expects — here, + who the license is for and its details: +

+ {% highlight "bash" %} +POST https://workflows.example/workflows/ambulance-license-issuance/exchanges + {% endhighlight %} + {% highlight "json" %} +{ + "ttl": 900, + "variables": { + "subject": "did:example:resident456", + "licenseClass": "Ambulance Driver", + "licenseNumber": "ADL-2026-00123", + "validFrom": "2026-01-15T00:00:00Z", + "validUntil": "2028-01-14T23:59:59Z" + } +} + {% endhighlight %} +

+ This call is authenticated (your service uses a capability or OAuth2 token). + On success the service returns 204 No Content with a + Location header — that's your exchange URL: +

+ {% highlight "http" %} +HTTP/1.1 204 No Content +Location: https://workflows.example/workflows/ambulance-license-issuance/exchanges/z19xQ... + {% endhighlight %} + +

2. Host and share the interaction URL

+

+ The exchange URL is the vcapi interaction endpoint. + Hosting and sharing it is the coordinator's responsibility — put it in a QR + code on a portal confirmation page, typically wrapped in a presentation + request the wallet understands: +

+ {% highlight "json" %} +{ + "VerifiablePresentation": { + "interact": { + "service": [{ + "type": "VerifiableCredentialApiExchangeService", + "serviceEndpoint": "https://workflows.example/workflows/ambulance-license-issuance/exchanges/z19xQ..." + }] + } + } +} + {% endhighlight %} +

+ The resident's wallet POSTs to that endpoint and runs the exchange with the + workflow service directly. DID authentication and credential delivery happen + there — you are not in that loop. +

+ +

3. Poll for the result

+

+ While the wallet works, GET the exchange URL (authenticated) until its + state is complete: +

+ {% highlight "bash" %} +GET https://workflows.example/workflows/ambulance-license-issuance/exchanges/z19xQ... + {% endhighlight %} + {% highlight "json" %} +{ + "exchange": { + "id": "z19xQ...", + "state": "complete", + "variables": { + "results": { + "didAuthn": { + "did": "did:example:resident456", + "verifiablePresentation": { "...": "the wallet's authenticated VP" } + } + } + } + } +} + {% endhighlight %} +

+ Verified per-step results land under + exchange.variables.results, keyed by the workflow's step names. + A complete state means the credential was issued into the + resident's wallet. +

+ +

That's it

+

+ A government issuer, as a coordinator, is these three calls: create + an exchange, host and share the interaction URL, poll for the result. + Signing the credential and speaking every wallet's protocol is the workflow + service's job — you just create exchanges and read what comes back. +

+ +

+ On the wallet end, a standalone client-side VCALM library is still planned; + until it ships, wallets run the exchange over the raw HTTP flow. The + coordinator side shown here works today against any VCALM workflow service, + such as one running + @bedrock/vc-delivery. +

+ +

Go deeper

+ + +

← Back to roles

+
diff --git a/src/verticals/government/build/verifier.njk b/src/verticals/government/build/verifier.njk new file mode 100644 index 0000000..beb580a --- /dev/null +++ b/src/verticals/government/build/verifier.njk @@ -0,0 +1,190 @@ +--- +title: "Build a verifier (business or service) — government IDs & licensing" +description: "Verify a government credential as a VCALM coordinator: create an exchange, share an interaction URL, poll for the verified result. Selective disclosure — prove over 21 without revealing home address." +permalink: /government/build/verifier/ +--- +{# Layer 4 — verifier coordinator. A coordinator is a CLIENT of a workflow + service; it does not run @bedrock/vc-delivery itself. It creates exchanges, + hosts/shares interaction URLs, and polls for verified results. The workflow + here requests only "age >= 21" or "valid license" — selective disclosure. #} +
+

Build a verifier

+

+ You run the relying-party side — a bar, a dispensary, an employer, an online + service. As a coordinator you speak VCALM — just the + coordinator side of it. You don't run VCALM delivery, the on-the-wire + exchange with the resident's wallet; a workflow service does that and + verifies the proof for you. Your job is three calls: create an exchange, share + an interaction URL that points the resident's wallet at it, then poll until the + verified result comes back. +

+ + + +

What the coordinator does

+
    +
  1. Create an exchange against a workflow that requests only + what you need — here, proof of age or proof of a valid license.
  2. +
  3. Host and share an interaction URL that points the + resident's wallet at that exchange.
  4. +
  5. Poll the exchange until it completes, then read the + verified result.
  6. +
+

+ The workflow service does the rest — it sends the presentation request, + receives the wallet's response, and verifies the proof, challenge, domain, + issuer, and status before it ever returns a result to you. +

+ +

1. Create the exchange

+

+ POST to your workflow's exchanges endpoint. The workflow already + encodes request only "age ≥ 21" and trust the issuing + agency; you pass just the per-exchange variables: +

+ {% highlight "bash" %} +POST https://workflows.example/workflows/age-check/exchanges + {% endhighlight %} + {% highlight "json" %} +{ + "ttl": 300, + "variables": { + "reason": "Please prove you are 21 or older to continue." + } +} + {% endhighlight %} +

+ This call is authenticated. On success the service returns + 204 No Content with a Location header — your + exchange URL: +

+ {% highlight "http" %} +HTTP/1.1 204 No Content +Location: https://workflows.example/workflows/age-check/exchanges/z19xQ... + {% endhighlight %} +

+ Because the workflow asks for a single derived attribute, a well-built wallet + returns proof of "age ≥ 21" — not the resident's name, exact birthdate, or + home address. The workflow's challenge and domain + stop a captured presentation from being replayed. +

+ +

2. Host and share the interaction URL

+

+ The exchange URL is the vcapi interaction endpoint. + Put it in a QR code at the point of sale, or embed it as a link in an + online flow — typically wrapped in a presentation request: +

+ {% highlight "json" %} +{ + "VerifiablePresentation": { + "interact": { + "service": [{ + "type": "VerifiableCredentialApiExchangeService", + "serviceEndpoint": "https://workflows.example/workflows/age-check/exchanges/z19xQ..." + }] + } + } +} + {% endhighlight %} +

+ The resident's wallet POSTs to that endpoint and runs the exchange with the + workflow service directly. You are not in that loop. +

+ +

3. Poll for the verified result

+

+ GET the exchange URL (authenticated) until its state is + complete: +

+ {% highlight "bash" %} +GET https://workflows.example/workflows/age-check/exchanges/z19xQ... + {% endhighlight %} + {% highlight "json" %} +{ + "exchange": { + "id": "z19xQ...", + "state": "complete", + "variables": { + "results": { + "ageCheck": { + "did": "did:example:resident456", + "verifiablePresentation": { "...": "verified: ageAtLeast21 = true" } + } + } + } + } +} + {% endhighlight %} +

+ A complete state means the workflow service already verified the + presentation for you — proof, challenge, domain, trusted issuer, and + revocation/expiry status. The verified result lands under + exchange.variables.results, keyed by the workflow's step names. + You received one derived fact — not the resident's ID. +

+ +

License check variant

+

+ The same three-call pattern works for checking professional licenses. + Configure a workflow that requests proof of a valid license class and trusts + the relevant licensing board: +

+ {% highlight "json" %} +{ + "ttl": 300, + "variables": { + "reason": "Please prove you hold a current ambulance driver certificate." + } +} + {% endhighlight %} +

+ A complete exchange confirms the license is current, not + expired, and signed by a trusted authority — without a real-time lookup to + the licensing board's registry. +

+ +

That's it

+

+ A relying-party verifier, as a coordinator, is these three calls: + create an exchange, host and share the interaction URL, poll for the + verified result. The cryptographic verification and protocol interop + are the workflow service's job — you create exchanges and read what comes + back. +

+ +

+ On the wallet end, a standalone client-side VCALM library is still planned; + until it ships, wallets run the exchange over the raw HTTP flow. The + coordinator side shown here works today against any VCALM workflow service, + such as one running + @bedrock/vc-delivery. +

+ +

Go deeper

+ + +

← Back to roles

+
diff --git a/src/verticals/government/build/wallet.njk b/src/verticals/government/build/wallet.njk new file mode 100644 index 0000000..5df981f --- /dev/null +++ b/src/verticals/government/build/wallet.njk @@ -0,0 +1,79 @@ +--- +title: "VCALM tutorial: build a wallet — government IDs & licensing" +description: "Make your wallet speak VCALM in 3 steps: dereference, POST to start, loop. HTTP example for government credentials — driver's licenses and professional licenses." +permalink: /government/build/wallet/ +--- +{# Layer 4 — exchange client. Same VCALM loop, government credential. #} +
+

Build a wallet

+

Make your wallet speak VCALM: pick up and present a government credential over an exchange.

+ +

You'll do exactly 3 things

+
    +
  1. Dereference an interaction URL to discover protocols.
  2. +
  3. POST to the exchange URL to start.
  4. +
  5. Loop: read verifiablePresentationRequest / + verifiablePresentation, POST back, until done.
  6. +
+ +

1. Discover protocols

+

A scanned QR code (from a DMV portal or relying-party service) encodes an interaction URL with iuv=1. GET it:

+ {% highlight "http" %} +GET /interactions/license-456?iuv=1 +Host: dmv.example.gov +Accept: application/json + {% endhighlight %} +

The response lists supported protocols; pick vcapi for VCALM:

+ {% highlight "json" %} +{ + "protocols": { + "vcapi": "https://vcapi.dmv.example.gov/exchanges/license-456" + } +} + {% endhighlight %} + +

2. Start the exchange

+

POST an empty body to the vcapi URL:

+ {% highlight "http" %} +POST /exchanges/license-456 +Host: vcapi.dmv.example.gov +Content-Type: application/json + +{} + {% endhighlight %} + +

3. Run the loop

+

+ When the server returns a verifiablePresentationRequest, find the + matching credential, build a signed Verifiable Presentation — disclosing only + the requested fields — and POST it back to the same exchange + URL. When the server returns a verifiablePresentation, + store the delivered credential. The exchange is done when the server stops + asking. +

+

+ For a verification exchange (age check, license check), the server returns a + verifiablePresentationRequest asking for a derived attribute. Your + wallet presents only that fact — not the full credential. The exchange + completes when the verifier's workflow service confirms the proof. +

+ +

That's it

+

A conformant government-credential wallet is this loop. Nothing more.

+ +
+ Prefer a library to raw HTTP? +

+ A dogfooded open-source VCALM exchange-client library is planned. Until + it ships, the HTTP flow above is the supported path. +

+
+ +

Go deeper

+ + +

← Back to roles

+
diff --git a/src/verticals/government/government.json b/src/verticals/government/government.json new file mode 100644 index 0000000..f141cce --- /dev/null +++ b/src/verticals/government/government.json @@ -0,0 +1,4 @@ +{ + "layout": "layouts/base.njk", + "tags": "government" +} diff --git a/src/verticals/government/index.njk b/src/verticals/government/index.njk new file mode 100644 index 0000000..8e697a6 --- /dev/null +++ b/src/verticals/government/index.njk @@ -0,0 +1,215 @@ +--- +title: "Government IDs & licensing with VCALM" +shortName: "Government IDs" +cardTitle: "Government IDs & licensing" +summary: "Issue driver's licenses, professional licenses, and age credentials residents control — verifiable anywhere, with any wallet, without revealing a full ID." +description: "Issue government credentials with W3C Verifiable Credentials over VCALM — resident-controlled, any wallet, no per-app certification, privacy by design. Prove over 21 without revealing your home address." +order: 3 +chooserIcon: governmentIssue +permalink: /government/ +--- +{# Single-page government-credentials vertical. Anchor: digital government IDs + and professional licenses issued by state agencies. + Distinct from student-id (campus identity) and education (diplomas) — + this is government-issued, high-stakes, real-world enforcement. Lean on + selective disclosure / one-fact proof (age, license status) without + revealing home address or exact birthdate. Secondary angle: no per-wallet + certification burden on the issuing agency. #} + +
+

{{ title }}

+

+ Issue government credentials that residents truly control — verifiable at a + bar, an employer, a government portal, or a partner service, with the + wallet of their choice, and without handing over a photocopy of their full ID. +

+

+ To issue a digital driver's license or professional license, an agency + signs it as a W3C Verifiable Credential and delivers it + to the resident's wallet of choice over VCALM (the VC API). + A business or service verifies it cryptographically — no real-time DMV + lookup — and with selective disclosure the resident can prove one fact, + like "over 21" or "licensed to operate," without revealing home address, + exact birthdate, or license number. +

+
+ +
+ {% set flowSteps = [ + {icon: flowIcons.governmentIssue, label: "Agency issues a credential", caption: "Driver's license, professional license, or age certificate"}, + {icon: flowIcons.governmentHold, label: "Resident holds and controls it", caption: "Nothing shared without one-tap, explicit consent"}, + {icon: flowIcons.governmentDisclose, label: "Proves one fact", caption: "Over 21 — no home address, no exact birthdate revealed"} + ] %} + {% set flowFooter = "Any conformant wallet — no agency-mandated app — selective disclosure by design" %} + {% include "partials/flow-diagram.njk" %} +
+ +
+

What it feels like to use

+

+ The technology disappears. Here is what each person actually + does — and what they no longer have to. +

+ +
    +
  1. +

    The agency issues a credential

    +

    + A resident applies through the agency's portal or in person. They + authenticate once, pick the wallet they already use, and the credential + lands in it — a digital copy of their license or certificate, signed + by the agency. +

    +

    No government-mandated app. Their wallet, their choice.

    +
  2. +
  3. +

    The resident holds and controls it

    +

    + The credential lives on their phone, under their control. Nothing is + shared automatically — every presentation needs an explicit, one-time + approval the resident sees and consents to. +

    +

    The agency cannot watch where it is used after issuance.

    +
  4. +
  5. +

    They prove just one thing, when needed

    +

    + At a bar, the resident taps to prove "age ≥ 21" — and reveals + nothing else. No home address, no exact birthdate, no license number. + At a job site, they prove "valid Class A CDL" and the employer + gets confirmation — not a full ID scan. +

    +

    Selective disclosure: minimum data, every time.

    +
  6. +
+ +
+
+

{{ flowIcons.person | safe }}What the resident experiences

+
    +
  • Their choice of wallet. Not a state-approved app.
  • +
  • Prove a fact, not your full ID. "Over 21" without your home address.
  • +
  • One-tap consent. Nothing leaves the phone without approval.
  • +
  • No tracking. The agency doesn't see each use.
  • +
+
+
+

{{ flowIcons.institution | safe }}What the agency experiences

+
    +
  • Privacy by design. Data minimization built into every request.
  • +
  • Instant, tamper-evident checks. No real-time DMV lookup.
  • +
  • No wallet certification burden. Any conformant wallet works.
  • +
  • Standards-based. Built on open W3C Verifiable Credentials.
  • +
+
+
+
+ +
+

Credential types

+

Any government-issued document can be expressed as a W3C Verifiable Credential:

+ +
+ +
+

How it works

+

One diagram, three actors, one loop.

+ +
+     AGENCY               RESIDENT              BUSINESS / SERVICE
+    (issuer)          (wallet of choice)           (verifier)
+     |                      |                          |
+     | -- delivers -->      |                          |
+     |  license VC           |                          |
+     |                      | <-- asks for --           |
+     |                      |   "over 21"               |
+     |                      | -- presents -->           |
+     |                      |   only "yes"              |
+     |                      |                     verifies (ok)
+  
+ + +
+ +
+

A good fit if…

+ +

+ Start building +

+
+ +
+

Common questions

+
+

Can I prove I am over 21 without revealing my exact birthdate?

+

+ Yes. With selective disclosure, a resident proves a single derived fact — + "age ≥ 21," "valid license" — without revealing name, exact birthdate, + home address, or license number. +

+ +

Does a digital government ID lock residents to a government app?

+

+ Not with VCALM — the credential works with any conformant wallet. The + ISO 18013-5 / mDL approach often requires device attestation and a + state-maintained list of certified apps. +

+ +

Is a VCALM government credential private?

+

+ Yes. Data minimization is built into every request, nothing is shared + without one-tap consent, and the agency cannot observe where the + credential is used. +

+ +

Why VCALM vs. mDL / ISO 18013-5?

+

+ The ISO 18013-5 mobile driver's license requires MDOC encoding, device + attestation, and state certification of every wallet. With VCALM, you + issue once to an open W3C standard and any conformant wallet works — + no per-app certification the agency has to own or maintain. + See the full comparison. +

+
+