Class Iso18013AnnexC
The verifier's side of an ISO/IEC 18013-7 Annex C presentation over the W3C Digital
Credentials API: builds the request pair the browser call carries, and opens the encrypted
response into DeviceResponse bytes plus the session transcripts device authentication
is verified over. Verification itself stays in MdocVerifier.
public static class Iso18013AnnexC
- Inheritance
-
Iso18013AnnexC
- Inherited Members
Methods
BuildSessionTranscript(byte[], string)
The session transcript for a request: what the response's HPKE keys are derived over, and what device authentication is signed over. One construction for both, because a wallet was observed using one.
public static byte[] BuildSessionTranscript(byte[] encryptionInfo, string origin)
Parameters
Returns
- byte[]
CreateRequest(string, string, IReadOnlyList<string>)
Builds the {deviceRequest, encryptionInfo} pair and the response key for one
request. The key is fresh per request and must never be reused: a stable advertised key
would let colluding verifiers correlate the people presenting to it.
public static Iso18013AnnexCRequest CreateRequest(string docType, string nameSpace, IReadOnlyList<string> elementIdentifiers)
Parameters
docTypestringnameSpacestringelementIdentifiersIReadOnlyList<string>
Returns
Remarks
The request is unsigned. ETSI TS 119 472-2 V1.2.1 ISO/IEC 18013-REQ-01 says "All the elements of the docRequests array shall contain the readerAuth member", and CIR (EU) 2026/1731 neither amends nor voids it. Its ISO/IEC 18013-7-API-01 has Annex C presentations comply "as further profiled in clauses 5.3 and 5.4", although its clause 5.1 calls clause 5.3 specific to the non-API mechanism. Read either way, a signed request meets it: for an EUDI Wallet, use CreateSignedRequest(string, string, IReadOnlyList<string>, string, MdocReaderKey, byte[]?).
CreateRequest(string, string, IReadOnlyList<string>, ZkRequest)
As CreateRequest(string, string, IReadOnlyList<string>), also asking the wallet for a zero knowledge proof of the elements.
public static Iso18013AnnexCRequest CreateRequest(string docType, string nameSpace, IReadOnlyList<string> elementIdentifiers, ZkRequest zkRequest)
Parameters
docTypestringThe requested document type.
nameSpacestringThe namespace the elements live in.
elementIdentifiersIReadOnlyList<string>The elements to request or to have proved.
zkRequestZkRequestThe circuits to offer and whether a proof is required.
Returns
Exceptions
- ArgumentException
No circuit
zkRequestoffers proves as many elements as are requested.
CreateRequest(string, string, IReadOnlyList<string>, ZkRequest?, ECDiffieHellman)
As CreateRequest(string, string, IReadOnlyList<string>), with the response key supplied by the caller rather than minted here. For a verifier whose answer may be opened by another instance or by this one after a restart: derive the key from a shared master and a fresh salt, store only the salt and derive the key again to open the answer.
public static Iso18013AnnexCRequest CreateRequest(string docType, string nameSpace, IReadOnlyList<string> elementIdentifiers, ZkRequest? zkRequest, ECDiffieHellman responseKey)
Parameters
docTypestringThe requested document type.
nameSpacestringThe namespace the elements live in.
elementIdentifiersIReadOnlyList<string>The elements to request or to have proved.
zkRequestZkRequestThe circuits to offer and whether a proof is required. Null asks for no proof.
responseKeyECDiffieHellmanA P-256 key holding its private part, which the wallet encrypts its response to.
Returns
Remarks
Use a new key for every request, as the minted path does. One long-lived key would tie separate presentations to one verifier, and whoever obtained it could open every answer ever encrypted to it. Each request still gets its own nonce, but that does not remove the link. Nothing here can tell a reused key from a new one, so this is the caller's to keep. A derived key also differs from a minted one in a way this method cannot change: while its salt is kept, the master derives it again. The key stays the caller's, and this method neither keeps nor disposes it.
Exceptions
- ArgumentException
responseKeyis not on P-256 or lacks its private part, or no circuitzkRequestoffers proves as many elements as are requested.
CreateSignedRequest(string, string, IReadOnlyList<string>, string, MdocReaderKey, byte[]?)
Builds the request pair with the docRequest signed by reader, so the wallet
can authenticate the reader. Otherwise as CreateRequest(string, string, IReadOnlyList<string>).
public static Iso18013AnnexCRequest CreateSignedRequest(string docType, string nameSpace, IReadOnlyList<string> elementIdentifiers, string origin, MdocReaderKey reader, byte[]? registrationCertificate)
Parameters
docTypestringThe requested document type.
nameSpacestringThe namespace the elements live in.
elementIdentifiersIReadOnlyList<string>The elements to request.
originstringThe origin of the page that will make the browser call, serialized as the browser reports it, for example
https://verifier.example.com: noorigin:prefix, no trailing slash or path, a lower-case ASCII host (punycode for an internationalised name), no default port; or an Android app'sandroid:apk-key-hash:origin. The usual mistakes (a missing scheme, a trailing slash, upper case, a default port) are refused, because the wallet would compute a different transcript and the signature would not verify. The check is a syntactic guard, not a URL parser: a few strings no browser reports, such as non-canonical IPv4, still pass it and fail at the wallet instead.readerMdocReaderKeyThe reader key and certificate path to sign with.
registrationCertificatebyte[]The relying party's registration certificate, exactly as issued, carried in the ItemsRequest's
requestInfoundereuWrprc, inside the signed bytes. Null for none, which CIR (EU) 2026/1731 does not allow in a request to an EUDI Wallet (its ISO/IEC 18013-5-REQ-04 and -REQ-06). Its REQ-07 asks for "a CBOR-encoded registration certificate", which read plainly means the CWT form of the two ETSI TS 119 475 allows. The bytes are passed through unchanged: which form they are is not checked here.
Returns
Remarks
The signature covers the session transcript, which commits to this request's EncryptionInfo
and to origin. A wallet reached from any other origin computes another
transcript, and the signature does not verify there.
CreateSignedRequest(string, string, IReadOnlyList<string>, string, MdocReaderKey, byte[]?, ZkRequest)
As CreateSignedRequest(string, string, IReadOnlyList<string>, string, MdocReaderKey, byte[]?),
also asking the wallet for a zero knowledge proof of the elements. The zkRequest sits inside the
bytes readerAuth signs.
public static Iso18013AnnexCRequest CreateSignedRequest(string docType, string nameSpace, IReadOnlyList<string> elementIdentifiers, string origin, MdocReaderKey reader, byte[]? registrationCertificate, ZkRequest zkRequest)
Parameters
docTypestringThe requested document type.
nameSpacestringThe namespace the elements live in.
elementIdentifiersIReadOnlyList<string>The elements to request or to have proved.
originstringThe origin of the page that will make the browser call; see the overload without
zkRequest.readerMdocReaderKeyThe reader key and certificate path to sign with.
registrationCertificatebyte[]The registration certificate, or null; see the overload without
zkRequest.zkRequestZkRequestThe circuits to offer and whether a proof is required.
Returns
Exceptions
- ArgumentException
No circuit
zkRequestoffers proves as many elements as are requested.
CreateSignedRequest(string, string, IReadOnlyList<string>, string, MdocReaderKey, byte[]?, ZkRequest?, ECDiffieHellman)
As CreateSignedRequest(string, string, IReadOnlyList<string>, string, MdocReaderKey, byte[]?), with the response key supplied by the caller rather than minted here. See CreateRequest(string, string, IReadOnlyList<string>, ZkRequest?, ECDiffieHellman) for when to use it and why each request needs a new key.
public static Iso18013AnnexCRequest CreateSignedRequest(string docType, string nameSpace, IReadOnlyList<string> elementIdentifiers, string origin, MdocReaderKey reader, byte[]? registrationCertificate, ZkRequest? zkRequest, ECDiffieHellman responseKey)
Parameters
docTypestringThe requested document type.
nameSpacestringThe namespace the elements live in.
elementIdentifiersIReadOnlyList<string>The elements to request or to have proved.
originstringThe origin of the page that will make the browser call; see the overload without a response key.
readerMdocReaderKeyThe reader key and certificate path to sign with.
registrationCertificatebyte[]The registration certificate, or null; see the overload without a response key.
zkRequestZkRequestThe circuits to offer and whether a proof is required. Null asks for no proof.
responseKeyECDiffieHellmanA P-256 key holding its private part, which the wallet encrypts its response to.
Returns
Exceptions
- ArgumentException
responseKeyis not on P-256 or lacks its private part. Also whenoriginis not a serialized origin and when no circuitzkRequestoffers proves as many elements as are requested.
OpenResponse(byte[], byte[], byte[], string)
Decrypts an EncryptedResponse. The HPKE keys are derived with the session transcript
as info and no aad. Throws AuthenticationTagMismatchException when the
key, the EncryptionInfo bytes or the origin do not match what the wallet sealed to.
public static Iso18013AnnexCResponse OpenResponse(byte[] encryptedResponse, byte[] responseKeyPkcs8, byte[] encryptionInfo, string origin)
Parameters
encryptedResponsebyte[]The wallet's response, decoded from base64url.
responseKeyPkcs8byte[]The stored ResponseKeyPkcs8.
encryptionInfobyte[]The stored EncryptionInfo, byte for byte.
originstringThe origin the browser presented the request from.
Returns
Remarks
The transcript is the plain one, whose second element is null. A wallet reached over the Digital Credentials API was observed on 2026-08-27 to derive its keys and sign device authentication over exactly that, and to produce an undecryptable response otherwise. Some implementations instead derive over a variant carrying the EncryptionParameters, which this does NOT use; the observed exchange is what governs here.
SealResponse(byte[], byte[], string)
Produces the EncryptedResponse a wallet would return for a request: seals
deviceResponse to the request's recipient key over the session
transcript, with a fresh ephemeral sender key. For tests, fixtures and mock wallets; a
verifier never seals in production.
public static byte[] SealResponse(byte[] deviceResponse, byte[] encryptionInfo, string origin)
Parameters
Returns
- byte[]