Explainer
Your AI agent can prove you're over 18 without holding your credential
For identity and security engineers. How a person can hand one fact from their own credential to an AI agent with Delegate SD-JWT, without giving the agent the credential or its key, and how a site checks the chain.
In An agent with a credential of its own, Ada’s agent signs in at Lantern Tickets with a credential that describes the agent: which agent it is and that Ada is responsible for it. Some sites need a fact about Ada herself. Friday’s late show at Lantern Hall is for over-18s only. Ada can prove her age with a credential in her own phone wallet, but the agent is doing the buying, and the agent shouldn’t hold Ada’s credential or her key.
Delegation solves this. Ada’s wallet signs a statement that says, roughly, “the holder of this other key may show my over-18 claim, under these conditions.” The agent’s wallet shows that statement, and the site can trace it back to the original issuer. Ada can sign it at the moment the site asks, or ahead of time. This article walks through how the Delegate SD-JWT draft does it, and what changes between the two.
What we’re starting from
- Ada’s wallet is the wallet on her phone. It holds an age credential from her government ID issuer: an SD-JWT VC (a signed credential whose fields can be shown one at a time). The credential is bound to a key in her phone. Its
cnf(confirmation) claim names that key, and only Ada’s wallet can sign with it. - The agent’s wallet is the separate service from the earlier articles. It holds the agent’s own credential and its keys. The agent can ask it to act, but can’t open it.
- Lantern Tickets asks for credentials with OpenID4VP (OpenID for Verifiable Presentations). For the late show it asks for two things: the agent credential, and proof that the person behind the agent is over 18.
Three shortcuts are tempting, and none works. Copying Ada’s credential into the agent’s wallet fails, because the credential is bound to Ada’s key and every presentation needs a fresh signature with that key. Giving the agent’s wallet Ada’s key turns the agent into Ada, for every credential that key protects, with no limits. Having Ada go to the site and present herself defeats the point of an agent.
The idea: the key binding becomes a credential
In plain SD-JWT (RFC 9901), a holder proves possession by adding a Key Binding JWT (KB-JWT). It’s a small JWT, signed with the key named in cnf, that carries the verifier’s identifier and a nonce. Delegate SD-JWT makes one change: the KB-JWT can itself be an SD-JWT. It’s then called a KB-SD-JWT, and it carries a delegate payload. That’s a small set of claims the holder signs for the next holder, and it includes the next holder’s key in its own cnf.
So the credential becomes a chain. Each link is signed by the key that the previous link names:
| Link | Signed by | Names this key in cnf |
Carries |
|---|---|---|---|
| Issuer-signed JWT | the ID issuer | Ada’s wallet key | Ada’s claims, selectively disclosable |
| KB-SD-JWT | Ada’s wallet key | the agent wallet’s key | the delegate payload: conditions Ada agreed to |
| KB-JWT | the agent wallet’s key | (none) | Lantern’s identifier and nonce for this sign-in |
The draft calls the first two links together a dSD-JWT, and all three a dSD-JWT+KB. The final KB-JWT is what lets the agent’s wallet prove, at Lantern, that it holds the key Ada delegated to. In compact form, the parts are joined with ~ as in any SD-JWT. An empty component separates one link’s disclosures from the next link:
<issuer-signed JWT>~<Ada's disclosures>~~<KB-SD-JWT>~<delegate disclosures>~<KB-JWT>
Ada’s KB-SD-JWT has its own typ, so a verifier can’t mistake it for an ordinary key binding:
// header
{ "typ": "kb+sd-jwt+kb", "alg": "ES256" }
// payload, signed with Ada's wallet key
{
"iat": 1792144800,
"aud": "x509_san_dns:wallet.example",
"nonce": "n-0S6_WzA2Mj",
"sd_hash": "Dy-RYwZfaaoC3inJbLslgPvMp09bH-clYP_3qbRqtW4",
"delegate_payload": [{ "...": "x7Qe1rUqZ4ivmSUVzpsxnKXeCC4GHp_TQ1Ql2E9ZwHA" }],
"_sd_alg": "sha-256"
}
delegate_payload is an array of digests, and the payload itself travels as a disclosure. The draft allows several payloads under one signature, so a wallet whose key needs a user action for every signature can delegate several things with one approval. When the agent’s wallet later presents, it discloses exactly one of them. sd_hash ties Ada’s signature to the issuer-signed JWT and the disclosures Ada chose. If Ada uses issuer_jwt_hash instead, she ties only to the issuer-signed JWT, and the agent’s wallet may drop more of her disclosures later.
The delegate payload is where the agent’s key and the conditions live:
{
"cnf": { "jwk": { "kty": "EC", "crv": "P-256", "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ" } },
"exp": 1792799999,
"allowed_verifier": "x509_san_dns:tickets.example"
}
cnf is the agent wallet’s key, and exp ends the delegation: here after Friday, as when Ada delegates ahead of time. The draft defines no names for conditions: it says an issuer or holder may add constraints as visible claims. allowed_verifier is our illustration, not a standard claim.
Two ways to delegate
The chain above doesn’t say when Ada signs. There are two sensible moments, and they lead to different trade-offs.
- On demand. Nothing happens until Lantern asks. At checkout the agent hands Lantern’s request to its wallet, the wallet finds it has no over-18 credential of its own, and it asks Ada’s wallet for a delegation there and then. It presents once and discards the delegation. This is the model in the A2WP draft.
- Ahead of time. Ada delegates before Friday, for example when she asks her agent to book the late show. The agent’s wallet keeps the delegated credential, never Ada’s own, and presents it at checkout without asking Ada again.
| On demand | Ahead of time | |
|---|---|---|
| When Ada signs | When Lantern asks | Before checkout |
| Ada’s involvement | One approval per sale, in her own wallet | One approval, then none until exp |
| What the agent’s wallet keeps | Nothing after the sale | The delegated credential, until exp |
Sensible exp |
Minutes | As long as the task needs |
| Calling it back | Nothing left to call back | Not possible; only exp and the conditions limit it |
In both, Ada approves in her own wallet, never on a page the agent controls, and the agent never sees her credential. The mechanics below are the same for both models. What differs is when step 1 happens and what the agent’s wallet does with the result.
Delegating
1. The request reaches Ada
The agent’s wallet has no channel to Ada’s phone, so it does what any cross-device OpenID4VP verifier does: it produces a request link. The agent passes the link to Ada in chat, and Ada opens it with her wallet.
On demand, the link comes back as the interaction on the agent’s pending checkout operation, and the agent relays it as it would any approval link. A2WP needs nothing new for this. Ahead of time, the agent’s wallet produces the link when Ada sets the delegation up.
2. The agent’s wallet and Ada’s wallet: OpenID4VP with a delegate transaction
Delegation reuses OpenID4VP rather than adding a protocol. The agent’s wallet plays the verifier. It sends a signed request with a DCQL query for the over-18 claim, plus one transaction_data entry of type delegate:
{
"type": "delegate",
"credential_ids": ["age"],
"format": "dSD-JWT+KB",
"delegate_payload_disclosure": "WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgeyJjbmYiOiB7Li4ufX1d"
}
The requester writes the delegate payload, including its own key and the conditions, and Ada’s wallet signs over its digest. Ada’s wallet checks the request’s signature like any OpenID4VP request, and must trust the agent’s wallet as a verifier, as it would any relying party. It shows Ada what is asked and on what terms, and asks her to approve. It then builds the KB-SD-JWT with her credential key and posts the result to the agent wallet’s response endpoint.
Ada’s age credential never leaves her control in a usable form. The agent’s wallet gets the over-18 disclosure, Ada’s signature over the delegate payload, and nothing it could use to sign as Ada. The agent itself sees none of it, only the outcome. On demand, the agent’s wallet moves straight on to step 3. Ahead of time, it keeps the chain until checkout.
Presenting
3. The agent’s wallet and Lantern Tickets: OpenID4VP
At checkout, the agent hands Lantern’s sign-in link to its wallet, exactly as in the earlier articles. On demand, this is the request that started the delegation; ahead of time, the wallet already holds it. Lantern’s DCQL query asks for the agent credential and an over-18 claim. The wallet answers with both. The agent credential works as before. For the delegated one, the wallet adds the final link: a KB-JWT signed with its own key, carrying Lantern’s identifier, Lantern’s nonce and an sd_hash over the whole chain before it.
4. Lantern Tickets: walk the chain
Lantern checks every link with the key the previous link names, and it checks the hashes in both directions. The draft is firm on the hashes: without them, a dishonest delegate could splice links from different chains together if Ada ever reused her key.
In order, Lantern checks:
- The issuer-signed JWT, as in RFC 9901. That covers the issuer’s signature, its validity dates, and that Lantern trusts this issuer.
- Ada’s KB-SD-JWT, verified as an SD-JWT with the key from the issuer JWT’s
cnf. Lantern also checks itstypand itssd_hash(orissuer_jwt_hash). - The delegate payload. Exactly one may be disclosed. Lantern applies its conditions: here
exp, and our illustrativeallowed_verifier. - The final KB-JWT, verified with the key from the delegate payload’s
cnf. Lantern checks its audience, nonce andsd_hash.
If every check passes, Lantern knows three things: the ID issuer said this holder is over 18; that holder delegated the claim to this key, on these terms; and this key signed the current sign-in. It answers the agent’s wallet with a redirect, which reaches the agent as the operation’s continuation. The agent opens it in its browser, and Lantern holds the seats.
What each party learns and can do
- Lantern learns the over-18 claim and that it was delegated. It also sees the issuer-signed JWT, which includes Ada’s wallet key in
cnf, and the agent wallet’s key. It doesn’t learn Ada’s other claims unless she disclosed them. - The agent’s wallet, on demand, presents the claim once and discards it. Ahead of time, it can present the claim until it expires, within the conditions Ada signed. Either way it can’t change the conditions, extend
expor add claims, because each of those would break Ada’s signature. - The agent can ask its wallet to present, as before. It never holds the delegated credential, Ada’s key or the wallet’s key.
- Ada can’t call a delegation back once it’s given. The draft says a holder can’t easily publish revocation information, and recommends a short
expand narrow conditions instead. On demand, that matters little: the delegation is used once and gone. Ahead of time,expand the conditions are her only limits.