Table of Contents

Class Iso18013AnnexC

Namespace
Tessio.Verifier.Core.Mdoc
Assembly
Tessio.Verifier.Core.Mdoc.dll

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

encryptionInfo byte[]
origin string

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

docType string
nameSpace string
elementIdentifiers IReadOnlyList<string>

Returns

Iso18013AnnexCRequest

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

docType string

The requested document type.

nameSpace string

The namespace the elements live in.

elementIdentifiers IReadOnlyList<string>

The elements to request or to have proved.

zkRequest ZkRequest

The circuits to offer and whether a proof is required.

Returns

Iso18013AnnexCRequest

Exceptions

ArgumentException

No circuit zkRequest offers 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

docType string

The requested document type.

nameSpace string

The namespace the elements live in.

elementIdentifiers IReadOnlyList<string>

The elements to request or to have proved.

zkRequest ZkRequest

The circuits to offer and whether a proof is required. Null asks for no proof.

responseKey ECDiffieHellman

A P-256 key holding its private part, which the wallet encrypts its response to.

Returns

Iso18013AnnexCRequest

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

responseKey is not on P-256 or lacks its private part, or no circuit zkRequest offers 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

docType string

The requested document type.

nameSpace string

The namespace the elements live in.

elementIdentifiers IReadOnlyList<string>

The elements to request.

origin string

The origin of the page that will make the browser call, serialized as the browser reports it, for example https://verifier.example.com: no origin: prefix, no trailing slash or path, a lower-case ASCII host (punycode for an internationalised name), no default port; or an Android app's android: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.

reader MdocReaderKey

The reader key and certificate path to sign with.

registrationCertificate byte[]

The relying party's registration certificate, exactly as issued, carried in the ItemsRequest's requestInfo under euWrprc, 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

Iso18013AnnexCRequest

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

docType string

The requested document type.

nameSpace string

The namespace the elements live in.

elementIdentifiers IReadOnlyList<string>

The elements to request or to have proved.

origin string

The origin of the page that will make the browser call; see the overload without zkRequest.

reader MdocReaderKey

The reader key and certificate path to sign with.

registrationCertificate byte[]

The registration certificate, or null; see the overload without zkRequest.

zkRequest ZkRequest

The circuits to offer and whether a proof is required.

Returns

Iso18013AnnexCRequest

Exceptions

ArgumentException

No circuit zkRequest offers 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

docType string

The requested document type.

nameSpace string

The namespace the elements live in.

elementIdentifiers IReadOnlyList<string>

The elements to request or to have proved.

origin string

The origin of the page that will make the browser call; see the overload without a response key.

reader MdocReaderKey

The reader key and certificate path to sign with.

registrationCertificate byte[]

The registration certificate, or null; see the overload without a response key.

zkRequest ZkRequest

The circuits to offer and whether a proof is required. Null asks for no proof.

responseKey ECDiffieHellman

A P-256 key holding its private part, which the wallet encrypts its response to.

Returns

Iso18013AnnexCRequest

Exceptions

ArgumentException

responseKey is not on P-256 or lacks its private part. Also when origin is not a serialized origin and when no circuit zkRequest offers 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

encryptedResponse byte[]

The wallet's response, decoded from base64url.

responseKeyPkcs8 byte[]

The stored ResponseKeyPkcs8.

encryptionInfo byte[]

The stored EncryptionInfo, byte for byte.

origin string

The origin the browser presented the request from.

Returns

Iso18013AnnexCResponse

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

deviceResponse byte[]
encryptionInfo byte[]
origin string

Returns

byte[]