RFC 9901

Selective Disclosure for JSON Web Tokens

📅 published November 2025, from ietf

RFC 9901: Selective Disclosure for JSON Web Tokens 🦋

Okay bestie, let's get into it 💖 This is an IETF RFC, which is an official spec doc that defines how an Internet protocol works. Here's the title block, with all the important deets up top ✨

  • Number and title: RFC 9901, Selective Disclosure for JSON Web Tokens
  • Authors: D. Fett (Authlete), K. Yasuda (Keio University), and B. Campbell (Ping Identity)
  • Category: Standards Track 🔥 (so it's an actual Internet Standard, not just an idea)
  • Date: November 2025
  • ISSN: 2070-1721
  • Obsoletes and updates: the title block doesn't list anything it obsoletes or updates

Abstract 💅

So the whole point of this spec is to define a way to selectively disclose individual elements of a JSON data structure that's used as the payload of a JSON Web Signature (JWS). The main use case is selectively disclosing JSON Web Token (JWT) claims 🌸

Status of This Memo ✨

This is an Internet Standards Track document.

It's a product of the Internet Engineering Task Force (IETF), so it represents the consensus of the IETF community. It got public review and was approved for publication by the Internet Engineering Steering Group (IESG) 🙌 If you want more on Internet Standards, check out Section 2 of RFC 7841.

For the current status of this document, any errata, and how to give feedback on it, go to https://www.rfc-editor.org/info/rfc9901 🎀

Copyright (c) 2025 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these docs carefully, because they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they're provided without warranty as described in the Revised BSD License 📜

Table of Contents 🧚

The TOC is kept exactly as the original has it, so you can find your way around the whole doc:

1.  Introduction
  1.1.  Feature Summary
  1.2.  Conventions and Terminology
2.  Flow Diagram
3.  Concepts
  3.1.  SD-JWT and Disclosures
  3.2.  Disclosing to a Verifier
  3.3.  Optional Key Binding
  3.4.  Verification
4.  SD-JWT and SD-JWT+KB Data Formats
  4.1.  Issuer-Signed JWT
    4.1.1.  Hash Function Claim
    4.1.2.  Key Binding
  4.2.  Disclosures
    4.2.1.  Disclosures for Object Properties
    4.2.2.  Disclosures for Array Elements
    4.2.3.  Hashing Disclosures
    4.2.4.  Embedding Disclosure Digests in SD-JWTs
    4.2.5.  Decoy Digests
    4.2.6.  Recursive Disclosures
  4.3.  Key Binding JWT
    4.3.1.  Binding to an SD-JWT
    4.3.2.  Validating the Key Binding JWT
5.  Example SD-JWT
  5.1.  Issuance
  5.2.  Presentation
6.  Considerations on Nested Data in SD-JWTs
  6.1.  Example: Flat SD-JWT
  6.2.  Example: Structured SD-JWT
  6.3.  Example: SD-JWT with Recursive Disclosures
7.  Verification and Processing
  7.1.  Verification of the SD-JWT
  7.2.  Processing by the Holder
  7.3.  Verification by the Verifier
8.  JWS JSON Serialization
  8.1.  New Unprotected Header Parameters
  8.2.  Flattened JSON Serialization
  8.3.  General JSON Serialization
  8.4.  Verification of the JWS JSON Serialized SD-JWT
9.  Security Considerations
  9.1.  Mandatory Signing of the Issuer-Signed JWT
  9.2.  Manipulation of Disclosures
  9.3.  Entropy of the Salt
  9.4.  Choice of a Hash Algorithm
  9.5.  Key Binding
  9.6.  Concealing Claim Names
  9.7.  Selectively Disclosable Validity Claims
  9.8.  Distribution and Rotation of Issuer Signature Verification
        Key
  9.9.  Forwarding Credentials
  9.10. Integrity of SD-JWTs and SD-JWT+KBs
  9.11. Explicit Typing
  9.12. Key Pair Generation and Lifecycle Management
10. Privacy Considerations
  10.1.  Unlinkability
  10.2.  Storage of User Data
  10.3.  Confidentiality During Transport
  10.4.  Decoy Digests
  10.5.  Issuer Identifier
11. IANA Considerations
  11.1.  JSON Web Token Claims Registration
  11.2.  Media Type Registrations
  11.3.  Structured Syntax Suffixes Registration
12. References
  12.1.  Normative References
  12.2.  Informative References
Appendix A.  Additional Examples
  A.1.  Simple Structured SD-JWT
  A.2.  Complex Structured SD-JWT
  A.3.  SD-JWT-Based Verifiable Credentials (SD-JWT VC)
  A.4.  W3C Verifiable Credentials Data Model v2.0
  A.5.  Elliptic Curve Key Used in the Examples
Appendix B.  Disclosure Format Considerations
Acknowledgements
Authors' Addresses

1. Introduction ✨

Okay so, JSON data gets passed between systems all the time, and it's often protected against being changed with JSON Web Signatures (JWSs) [RFC7515] 🔐 A super popular use of JWS is the JSON Web Token (JWT) [RFC7519], which is a format that's usually used to represent a user's identity. Like, an ID Token from OpenID Connect [OpenID.Core] is a JWT that holds the user's claims and is created by the server for a relying party to consume 💅 When the JWT goes straight from the server to the relying party (like in OpenID Connect), the server picks which user claims go in at issuance time. That means it can keep the info shared with the relying party who validates the JWT to a minimum 🦋

But there's a newer model that totally decouples issuing a JWT from presenting it 👑 In this model, a JWT with lots of claims gets issued to a middle party who holds it, called the Holder. The Holder can then present that JWT to different verifying parties (Verifiers), and each one might only need some of the claims in it. For example, the JWT might have claims for both an address and a birthdate. The Holder could choose to disclose only the address to one Verifier, and only the birthdate to a different one 🌸

Privacy principles of minimal disclosure, paired with this model, call for a way to selectively disclose data elements while Verifiers can still check that the data is authentic 🫶 That's exactly what this spec defines, for JSON payloads of JWSs, with JWTs as the main use case.

So, Selectively Disclosable JWT (SD-JWT) is built on something called "salted hashes" 🧂 For any data element that should be selectively disclosable, the Issuer of the SD-JWT doesn't put the cleartext of the data in the JSON payload of the JWS. Instead, a digest of the data takes its place. When it's time to present to a Verifier, the Holder sends the signed payload along with the cleartext of just the claims they want to disclose. The Verifier can then compute the digest of that cleartext and confirm it's included in the signed payload ✅ To make sure Verifiers can't guess the cleartext values of data that wasn't disclosed, an extra salt value gets used when creating the digest, and it's sent along with the cleartext when the data is disclosed 🤫

To stop attacks where an SD-JWT gets presented to a Verifier without the Holder's consent, this spec also defines a way to bind the SD-JWT to a key the Holder controls (Key Binding) 🔑 When Key Binding is enforced, the Holder has to prove they possess a private key that belongs to a public key contained in the SD-JWT itself. They usually do this by signing over a data structure that has transaction-specific data in it, which this spec calls the Key Binding JWT 💜 An SD-JWT with a Key Binding JWT is called "SD-JWT+KB" in this specification.

1.1. Feature Summary 🎀

This spec defines two primary data formats:

  1. SD-JWT is a composite structure made of a JWS plus optional Disclosures, which lets you selectively disclose parts of the JWS payload. It includes the following:
    • A format for selective disclosure in nested JSON data structures, supporting selectively disclosable object properties (name/value pairs) and array elements.
    • A format for encoding the selectively disclosable data items.
    • A format that extends the JWS Compact Serialization, so the Issuer-signed JSON data structure and the disclosable data items can travel together.
    • An alternate format that extends the JWS JSON Serialization, which also carries the Issuer-signed JSON data structure and Disclosure data 🧚
  2. SD-JWT+KB is a composite structure of an SD-JWT and a cryptographic Key Binding, which can be presented to and verified by the Verifier 🔥 It includes the following:
    • A mechanism for associating an SD-JWT with a key pair.
    • A format for a Key Binding JWT (KB-JWT) that allows proof of possession of the private key of the associated key pair.
    • A format that extends the SD-JWT format for the combined transport of the SD-JWT and the KB-JWT 💖

1.2. Conventions and Terminology

okay bestie, time for the vocab 💅 The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. 🎀

Base64url: this is the URL-safe base64 encoding without padding, defined in Section 2 of [RFC7515]. 🌸

Claim: basically, this refers to object properties (name/value pairs) and array elements in this doc. ✨

Selective Disclosure: the process where a Holder reveals to a Verifier only a subset of the claims in a JWT that an Issuer gave them. 🫶

Selectively Disclosable JWT (SD-JWT): this is a composite structure made of an Issuer-signed JWT (JWS; see [RFC7515]) and zero or more Disclosures. It supports selective disclosure as defined in this document. It can hold both regular claims and digests of selectively disclosable claims. 🦋

Disclosure: a base64url-encoded string of a JSON array. Inside it you get a salt, a claim name (present when the claim is a name/value pair and absent when it's an array element), and a claim value. The Disclosure is used to calculate a digest for its claim, and the term Disclosure refers to the whole base64url-encoded string. 💜

Key Binding: the Holder's ability to prove they have an SD-JWT by proving control over a private key during the presentation. When Key Binding is in use, the SD-JWT contains the public key that matches the private key the Holder controls (or a reference to that public key). 🔑

Key Binding JWT (KB-JWT): a Key Binding JWT is said to "be tied to" a particular SD-JWT when its payload is signed with the key included in the SD-JWT payload, and the KB-JWT contains a hash of the SD-JWT in its sd_hash claim. Its format is defined in Section 4.3. 🧚

Selectively Disclosable JWT with Key Binding (SD-JWT+KB): a composite structure made of an SD-JWT plus a Key Binding JWT tied to that SD-JWT. 👑

Processed SD-JWT Payload: the JSON object you get after verifying and processing the Issuer-signed SD-JWT, with digest placeholders swapped out for the matching values from the Disclosures. ⭐

Issuer: an entity that creates SD-JWTs. 🏭

Holder: an entity that received SD-JWTs from the Issuer and has control over them. In this document, the term may refer to the actual user, the supporting hardware and software they own, or both. 🌈

Verifier: an entity that requests, checks, and extracts the claims from an SD-JWT along with its respective Disclosures. 🔍


2. Flow Diagram ✨

okay so this diagram is giving full journey 🍓

              +------------+
              |            |
              |   Issuer   |
              |            |
              +------------+
                    |
               Issues SD-JWT
         including all Disclosures
                    |
                    v
              +------------+
              |            |
              |   Holder   |
              |            |
              +------------+
                    |
        Presents SD-JWT or SD-JWT+KB
       including selected Disclosures
                    |
                    v
              +-------------+
              |             |+
              |  Verifiers  ||+
              |             |||
              +-------------+||
               +-------------+|
                +-------------+

              Figure 1: SD-JWT Issuance and Presentation Flow

3. Concepts 🦋

this section is the big-picture version of SD-JWTs, their Disclosures, and Key Binding. It skips the nitty-gritty data formats (those come in Section 4) 💅

3.1. SD-JWT and Disclosures

an SD-JWT, at its core, is a digitally signed JSON document with digests over the selectively disclosable claims, and the Disclosures live outside the document. Disclosures can be left out without breaking the signature, and changes to them can be detected. Selectively disclosable claims can be individual object properties (name/value pairs) or array elements. 🎀

each digest value keeps things intact and maps back to its Disclosure. Digest values are calculated with a hash function over the Disclosures. Each one has a cryptographically secure random salt, the claim name (only when the claim is an object property), and the claim value. The Disclosures get sent to the Holder with the SD-JWT in the format from Section 4. When presenting an SD-JWT to a Verifier, the Holder only includes the Disclosures for the claims they want to reveal to that particular Verifier. 🌸

An SD-JWT MAY also contain cleartext claims that are always disclosed to the Verifier. 👀

3.2. Disclosing to a Verifier

to share just a subset of the SD-JWT claim values with a Verifier, the Holder sends only the Disclosures for those selectively released claims, as part of the SD-JWT. 💖

3.3. Optional Key Binding

Key Binding is an optional feature. When Key Binding is required by the use case, the SD-JWT MUST contain information about the key material controlled by the Holder. 🔐

Note: How the public key is included in SD-JWT is described in Section 4.1.2.

when a Verifier requires Key Binding, the Holder presents an SD-JWT+KB, which is an SD-JWT plus a Key Binding JWT tied to that SD-JWT. The Key Binding JWT encodes a signature by the Holder's private key over:

  • a hash of the SD-JWT,
  • a nonce to ensure the freshness of the signature, and
  • an audience value to indicate the intended Verifier for the document.

Details of the format of Key Binding JWTs are described in Section 4.3. 🧁

3.4. Verification

at a high level, the Verifier:

  • receives either an SD-JWT or an SD-JWT+KB from the Holder,
  • verifies the signature on the SD-JWT (or the SD-JWT inside the SD-JWT+KB) using the Issuer's public key,
  • verifies the signature on the KB-JWT using the public key included (or referenced) in the SD-JWT, if the Verifier's policy requires Key Binding, and
  • calculates the digests over the Holder-Selected Disclosures and verifies that each digest is contained in the SD-JWT.

The detailed algorithm is described in Section 7.3. 🕵️‍♀️


4. SD-JWT and SD-JWT+KB Data Formats 💅

an SD-JWT is made of:

  • an Issuer-signed JWT, and
  • zero or more Disclosures.

an SD-JWT+KB is made of:

  • an SD-JWT (i.e., an Issuer-signed JWT and zero or more Disclosures), and
  • a Key Binding JWT.

The Issuer-signed JWT, Disclosures, and Key Binding JWT are explained in Sections 4.1, 4.2, and 4.3, respectively. 📚

the compact serialized format for the SD-JWT is the concatenation of each part, separated by a single tilde ('~') character, like this (where "D.1" to "D.N" stand for the respective Disclosures):

<Issuer-signed JWT>~<D.1>~<D.2>~...~<D.N>~

The order of the concatenated parts MUST be the Issuer-signed JWT, a tilde character, zero or more Disclosures each followed by a tilde character, and lastly the optional Key Binding JWT. In the case that there is no Key Binding JWT, the last element MUST be an empty string and the last separating tilde character MUST NOT be omitted. 🙈

the serialized format for an SD-JWT+KB extends the SD-JWT format by concatenating a Key Binding JWT:

<Issuer-signed JWT>~<D.1>~<D.2>~...~<D.N>~<KB-JWT>

You can tell the two formats apart by the final ~ character that's present on an SD-JWT. A Verifier that expects an SD-JWT MUST verify that the final tilde-separated component is empty. A Verifier that expects an SD-JWT+KB MUST verify that its final tilde-separated component is a valid KB-JWT. 🧐

The Disclosures are linked to the Issuer-signed JWT through the digest values included therein. 🔗

when issuing to a Holder, the Issuer includes all the relevant Disclosures in the SD-JWT. 📦

when presenting to a Verifier, the Holder sends only the selected set of Disclosures in the SD-JWT. ✨

The Holder MAY send any subset of the Disclosures to the Verifier, i.e., none, some, or all Disclosures. For data that the Holder does not want to reveal to the Verifier, the Holder MUST NOT send Disclosures or reveal the salt values in any other way. A Holder MUST NOT send a Disclosure that was not included in the issued SD-JWT or send a Disclosure more than once. 🚫

to show the SD-JWT format more clearly, here are a few different SD-JWT versions, with and without different parts.

An SD-JWT without Disclosures:

<Issuer-signed JWT>~

An SD-JWT with Disclosures:

<Issuer-signed JWT>~<Disclosure 1>~<Disclosure N>~

An SD-JWT+KB without Disclosures:

<Issuer-signed JWT>~<KB-JWT>

An SD-JWT+KB with Disclosures:

<Issuer-signed JWT>~<Disclosure 1>~<Disclosure N>~<KB-JWT>

as another way to look at the SD-JWT format, here's the ABNF [RFC5234] for the SD-JWT, SD-JWT+KB, and the various constituent parts (for those who celebrate 🎄):

ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
DIGIT = %x30-39 ; 0-9
BASE64URL = 1*(ALPHA / DIGIT / "-" / "_")
JWT = BASE64URL "." BASE64URL "." BASE64URL
DISCLOSURE = BASE64URL
SD-JWT = JWT "~" *(DISCLOSURE "~")
KB-JWT = JWT
SD-JWT-KB = SD-JWT KB-JWT

4.1. Issuer-Signed JWT

an SD-JWT has a JWT component that MUST be signed using the Issuer's private key. It MUST NOT use the none algorithm. 🔏

the payload of an SD-JWT is a JSON object that follows these rules:

  1. The payload MAY contain the _sd_alg key described in Section 4.1.1.
  2. The payload MAY contain one or more digests of Disclosures to enable selective disclosure of the respective claims, created and formatted as described in Section 4.2.
  3. The payload MAY contain one or more decoy digests to obscure the actual number of claims in the SD-JWT, created and formatted as described in Section 4.2.5.
  4. The payload MAY contain one or more permanently disclosed claims.
  5. The payload MAY contain the Holder's public key(s) or reference(s) thereto, as explained in Section 4.1.2.
  6. The payload MAY contain further claims such as iss, iat, etc. as defined or required by the application using SD-JWTs.
  7. The payload MUST NOT contain the claims _sd or ... except for the purpose of conveying digests as described in Sections 4.2.4.1 and 4.2.4.2, respectively.

The same digest value MUST NOT appear more than once in the SD-JWT. 🙅‍♀️

Application and profiles of SD-JWT SHOULD be explicitly typed. See Section 9.11 for more details. 🏷️

It's the Issuer who decides which claims are selectively disclosable by the Holder and which aren't. Claims MAY be included as plaintext as well, e.g., if hiding the particular claims from the Verifier isn't required in the intended use case. See Section 9.7 for considerations on making validity-controlling claims such as exp selectively disclosable. 👑

Claims that are not selectively disclosable are included in the SD-JWT in plaintext, just as they would be in any other JSON structure. 💖

4.1.1. Hash Function Claim ✨

okay so the _sd_alg claim tells you which hash algorithm the Issuer used to make the digests in Section 4.2 🔐. When it's used, it MUST appear at the top level of the SD-JWT payload. It MUST NOT be used in any object nested inside the payload. If _sd_alg isn't at the top level, the default is sha-256, and that MUST be used 💅.

The claim value is a case-sensitive string with the hash algorithm identifier. That identifier MUST be a hash algorithm value from the "Hash Name String" column in the "Named Information Hash Algorithm Registry" [Hash.Algs], or a value defined in another specification and/or profile of this specification 🦋.

To promote interoperability, implementations MUST support the sha-256 hash algorithm 🫶.

See Section 9 for the requirements on salt entropy, minimum salt length, and choosing a hash algorithm 🌸.

4.1.2. Key Binding 🗝️

If the Issuer wants to enable Key Binding, it includes a public key associated with the Holder, or a reference to one, using the cnf claim as defined in [RFC7800] 🎀. The jwk confirmation method, as defined in Section 3.2 of [RFC7800], is suggested for doing this, but other confirmation methods can be used too ✨.

Heads up, bestie 👀: as [RFC7800] already said, if an app needs to represent multiple proof-of-possession keys in the same SD-JWT, one way to do it is to use other claim names, alongside cnf, to hold the extra proof-of-possession key information 🧚.

Setting up the Holder's key pair is outside the scope of this document 🙈. For example, the Holder MAY create a key pair and give a public key to the Issuer, the Issuer MAY create the key pair for the Holder, or the Holder and Issuer MAY use key material they already set up together 💜.

Note: the examples throughout this document use the cnf claim with the jwk member to include the raw public key by value in SD-JWT 💖.

4.2. Disclosures 🌈

Disclosures are created differently depending on whether a claim is an object property (name/value pair) or an array element 💅.

  • For a claim that is an object property, the Issuer creates a Disclosure as described in Section 4.2.1.
  • For a claim that is an array element, the Issuer creates a Disclosure as described in Section 4.2.2.

4.2.1. Disclosures for Object Properties 🎀

For each claim that is an object property and that is to be made selectively disclosable, the Issuer MUST create a Disclosure like this 🪄:

  • Create a JSON array of three elements, in this order:
    1. A salt value. It MUST be a string. See Section 9.3 for the security considerations 🧂. To get the recommended entropy in the salt, the Issuer can base64url-encode 128 bits of cryptographically secure random data, which produces a string. The salt value MUST be unique for each claim that is to be selectively disclosed. The Issuer MUST NOT reveal the salt value to any party other than the Holder 🤫.
    2. The claim name, or key, as it would be used in a regular JWT payload. It MUST be a string and MUST NOT be _sd, ..., or a claim name that already exists in the object as a permanently disclosed claim 🙅‍♀️.
    3. The claim value, as it would be used in a regular JWT payload. The value can be of any type that is allowed in JSON, including numbers, strings, booleans, arrays, null, and objects 🍓.
  • base64url-encode the UTF-8 byte sequence of the JSON array. This string is the Disclosure 💌.
Note: the order was picked for readability 📖. Salts have a constant length within the SD-JWT, claim names are usually about the same length, and claim values vary in size, sometimes being big objects.

Here's the example that walks through those steps 🦋. First, the array:

["_26bc4LT-ac6q2KI6cBW5es", "family_name", "Möbius"]

And the resulting Disclosure:

WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzI
l0

Okay, here's the part I'm obsessed with 😭. Whitespace variations, how Unicode characters are encoded, the ordering of object properties, and so on are all allowed in the JSON representation, and you don't need to canonicalize anything before base64url encoding, because the digest is calculated over the base64url-encoded value itself. For example, all of these are valid and encode the same claim value "Möbius" 💫:

  • A different way to encode the Unicode umlaut:
WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNXHUwMG
Y2Yml1cyJd
  • No white space:
WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsImZhbWlseV9uYW1lIiwiTcO2Yml1cy
Jd
  • Newline characters between elements:
WwoiXzI2YmM0TFQtYWM2cTJLSTZjQlc1ZXMiLAoiZmFtaWx5X25hbWUiLAoiTcO2Ym
l1cyIKXQ

Here's the twist 🌀: the digest is calculated over the respective base64url-encoded value, so the variation the Issuer picked gets effectively signed and locked in place for that particular SD-JWT 🔒.

See Appendix B for some more thoughts on the Disclosure format approach 📚.

4.2.2. Disclosures for Array Elements 🍓

For each claim that is an array element and that is to be made selectively disclosable, the Issuer MUST create a Disclosure like this 🌷:

  • The array MUST contain two elements, in this order:
    1. The salt value, as described in Section 4.2.1 🧂.
    2. The array element that is to be hidden. This value can be of any type that is allowed in JSON, including numbers, strings, booleans, arrays, and objects 🙈.

The Disclosure string is made by base64url-encoding the UTF-8 byte sequence of the resulting JSON array, as described in Section 4.2.1. The same considerations about variations in the JSON encoding apply here too 💅.

For example, take a Disclosure for the second element of the nationalities array in this JWT Claims Set:

{
  "nationalities": ["DE", "FR", "US"]
}

You could create it by first making this array:

["lklxF5jMYlGTPUovMNIvCA", "FR"]

And the resulting Disclosure would be:

WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0
Note: the size of an array alone can accidentally reveal stuff you didn't mean to share 😬. Decoys, as described in Section 4.2.5, can be used to consistently pad the size of an array, which helps hide how many elements are actually in any particular instance 🫣.

4.2.3. Hashing Disclosures 🔐

To embed references to the Disclosures in the SD-JWT, each Disclosure is hashed using the hash algorithm named in the _sd_alg claim from Section 4.1.1, or SHA-256 if no algorithm is named 🧁. The resulting digest then goes into the SD-JWT payload instead of the original claim value, as described next ✨.

The digest MUST be computed over the US-ASCII bytes of the base64url-encoded value that is the Disclosure. This follows the convention in JWS [RFC7515] and JWE [RFC7516] 🎀. The bytes of the digest MUST then be base64url encoded 🌸.

Okay, important stuff, bestie 🚨:

  • The input to the hash function MUST be the base64url-encoded Disclosure, not the bytes that the base64url string encodes 🧠.
  • The bytes of the hash function's output MUST be base64url encoded. They are not the bytes that make up the (sometimes used) hex representation of the digest's bytes 🙅‍♀️.

For example, the base64url-encoded SHA-256 digest of the Disclosure for the family_name claim from Section 4.2.1 above is:

W
yJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl
0
X9yH0Ajrdm1Oij4tWso9UzzKJvPoDxwmuEcO3XAdRC0

4.2.4. Embedding Disclosure Digests in SD-JWTs 🧚

For selectively disclosable claims, the digests of the Disclosures get embedded into the Issuer-signed JWT in place of the claims themselves 🦋. How you embed them depends on whether a claim is an object property (name/value pair) or an array element 💖.

  • For a claim that is an object property, the Issuer embeds a Disclosure digest as described in Section 4.2.4.1.
  • For a claim that is an array element, the Issuer creates a Disclosure digest as described in Section 4.2.4.2.

4.2.4.1. Object Properties 🌷

Digests of Disclosures for object properties are added to an array under the new key _sd in the object. The _sd key MUST refer to an array of strings, where each string is either a digest of a Disclosure or a decoy digest as described in Section 4.2.5 🎀. An _sd key can show up at any level of the JSON object hierarchy: at the top level, nested deeper as described in Section 6, or in recursive Disclosures as described in Section 4.2.6 🪆.

The array MAY be empty if the Issuer decided not to selectively disclose any claims at that level. However, it is RECOMMENDED to omit the _sd key in this case to save space 💅.

The Issuer MUST hide the original order of the claims in the array 🙈. To make that happen, it is RECOMMENDED to shuffle the array of hashes, for example by sorting it alphanumerically or randomly, after potentially adding decoy digests as described in Section 4.2.5 🔀. The exact method doesn't matter, as long as it doesn't depend on the original order of the elements ✨.

For example, using the digest of the Disclosure from Section 4.2.3, the Issuer could create this SD-JWT payload to make family_name selectively disclosable 💌:

{
  "given_name": "Alice",
  "_sd": ["X9yH0Ajrdm1Oij4tWso9UzzKJvPoDxwmuEcO3XAdRC0"]
}

4.2.4.2. Array Elements 🍓

Digests of Disclosures for array elements are added to the array in the same position as the original claim value was in the array 📍. For each digest, an object of the form {"...": "<digest>"} gets added to the array. The key MUST always be the string ... (three dots) 🫶. The value MUST be the digest of the Disclosure created as described in Section 4.2.3. There MUST NOT be any other keys in the object 🙅‍♀️. Fun fact: the string ... was picked because the ellipsis character, which is usually typed as three period characters, is commonly used wherever content is left out of the current context 🌈.

For example, using the digest of the array element Disclosure from Section 4.2.2, the Issuer could create this SD-JWT payload to make the second element of the nationalities array selectively disclosable 🦋:

{
  "nationalities":
    ["DE", {"...":"w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs"},
     "US"]
}

As described in Section 7.3, Verifiers ignore all selectively disclosable array elements they did not receive a Disclosure for 👀. So in the example above, verification would output an array with only two elements, ["DE", "US"], unless the matching Disclosure for the second element is received. If it is, the output becomes a three-element array, ["DE", "FR", "US"] 🎉.

4.2.5. Decoy Digests 🎭

okay so an Issuer MAY add extra digests to the SD-JWT payload that aren't tied to any claim at all 🫣 These "decoy" digests exist to make it harder for a nosy Verifier to count how many claims or array elements are actually in the SD-JWT. Decoy digests MAY be added to the _sd array for objects, and also inside arrays too 🎀

It is RECOMMENDED to make decoy digests by hashing a cryptographically secure random number 🎲 Then the bytes of the digest MUST be base64url encoded, same as before 💅 And the same digest function used for the Disclosures MUST be used here too, no switching ✨

For decoy digests, no Disclosure gets sent to the Holder. That means the Holder sees digests that don't match any Disclosure 🙈 Check out Section 10.4 for more privacy vibes 🔐

The examples in this spec don't include decoy digests unless we explicitly say so, so they stay readable and replicable 📖 If you want an example with decoys, peep Appendix A.1 🦋

4.2.6. Recursive Disclosures 🌀

The algorithms above are compatible with "recursive Disclosures", where one selectively disclosed field reveals that there are even more selectively disclosable fields inside it. Russian doll energy, honestly 🪆 Here's a JSON structure:

{
    "family_name": "Möbius",
    "nationalities": ["DE", "FR", "UK"]
}

When the Holder has several nationalities, the Issuer may wish to hide the fact that there are any nationality statements at all, while still letting the Holder reveal each nationality one at a time 👀 To do that, first make each entry in the "nationalities" array selectively disclosable, then make the whole "nationalities" field selectively disclosable too. Layers, bestie 💖

Here's each entry in the "nationalities" array made selectively disclosable:

{
    "family_name": "Möbius",
    "nationalities": [
        { "...": "PmnlrRjhLcwf8zTDdK15HVGwHtPYjddvD362WjBLwro" }
        { "...": "r823HFN6Ba_lpSANYtXqqCBAH-TsQlIzfOK0lRAFLCM" },
        { "...": "nP5GYjwhFm6ESlAeC4NCaIliW4tz0hTrUeoJB3lb5TA" }
    ]
}

Content of Disclosures:

PmnlrRj... = ["16_mAd0GiwaZokU26_0i0h","DE"]
r823HFN... = ["fn9fN0rD-fFs2n303ZI-0c","FR"]
nP5GYjw... = ["YIKesqOkXXNzMQtsX_-_lw","UK"]

Then we make the whole "nationalities" array selectively disclosable too 🫶

{
    "family_name": "Möbius",
    "_sd": [ "5G1srw3RG5W4pVTwSsYxeOWosRBbzd18ZoWKkC-hBL4" ]
}

Content of Disclosures:

PmnlrRj... = ["16_mAd0GiwaZokU26_0i0h","DE"]
r823HFN... = ["fn9fN0rD-fFs2n303ZI-0c","FR"]
nP5GYjw... = ["YIKesqOkXXNzMQtsX_-_lw","UK"]
5G1srw3... = ["4drfeTtSUK3aY_-PF12gcX","nationalities",
    [
        { "...": "PmnlrRjhLcwf8zTDdK15HVGwHtPYjddvD362WjBLwro" },
        { "...": "r823HFN6Ba_lpSANYtXqqCBAH-TsQlIzfOK0lRAFLCM" },
        { "...": "nP5GYjwhFm6ESlAeC4NCaIliW4tz0hTrUeoJB3lb5TA" }
    ]
]

With this set of Disclosures, the Holder could include the Disclosure with hash PmnlrRj... to reveal only the "DE" nationality 🇩🇪. Or they could include both PmnlrRj... and r823HFN... to reveal "DE" and "FR", while keeping "UK" hidden 🤫. Either way, the Holder would also need to include the Disclosure with hash 5G1srw3... to reveal the nationalities field that holds those entries 🧩

Making recursive redactions creates dependencies between the Disclosure objects in an SD-JWT. The r823HFN... Disclosure can't be used without the 5G1srw3... Disclosure, because a Verifier wouldn't have a matching hash telling it where the content of the r823HFN... Disclosure should go 🙃 So if a Disclosure object is included in an SD-JWT, then the SD-JWT MUST include any other Disclosure objects needed to process that first one. In other words, any Disclosure object in an SD-JWT has to "connect" to the claims in the issuer-signed JWT, possibly through an intermediate Disclosure object 🔗

In the example above, it would be illegal to include any one of the PmnlrRj..., r823HFN..., or nP5GYjw... Disclosure objects without also including the 5G1srw3... Disclosure object. 🚫

4.3. Key Binding JWT 🔑

This section defines the Key Binding JWT, which encodes a signature over an SD-JWT made with the Holder's private key ✍️

The Key Binding JWT MUST be a JWT according to [RFC7519], and it MUST contain the following elements:

  • in the JOSE header,
    • typ: REQUIRED. MUST be kb+jwt, which explicitly types the Key Binding JWT as recommended in Section 3.11 of [RFC8725] 🏷️
    • alg: REQUIRED. A digital signature algorithm identifier, such as one from the IANA "JSON Web Signature and Encryption Algorithms" registry. It MUST NOT be "none" 🚫
  • in the JWT payload,
    • iat: REQUIRED. The value of this claim MUST be the time at which the Key Binding JWT was issued, using the syntax defined in [RFC7519] ⏰
    • aud: REQUIRED. The value MUST be a single string that identifies the intended receiver of the Key Binding JWT. How that value gets represented is up to the protocol in use, and it's out of scope for this specification 📬
    • "nonce": REQUIRED. Makes sure the signature is fresh or bound to the given transaction. The value type of this claim MUST be a string. How this value is obtained is up to the protocol in use, and it's out of scope for this specification 🌸
    • sd_hash: REQUIRED. The base64url-encoded hash value over the Issuer-signed JWT and the selected Disclosures, as defined below 💜

The general extensibility model of JWT means that additional claims and header parameters can be added to the Key Binding JWT. However, unless there's a compelling reason, this SHOULD be avoided, since it may harm interoperability and burden conceptual integrity 🙅‍♀️

4.3.1. Binding to an SD-JWT 🧷

The hash value in the sd_hash claim binds the KB-JWT to that specific SD-JWT. The sd_hash value MUST be computed over the US-ASCII bytes of the encoded SD-JWT: the Issuer-signed JWT, a tilde character, and zero or more Disclosures selected for presentation to the Verifier, each followed by a tilde character:

<Issuer-signed JWT>~<Disclosure 1>~<Disclosure 2>~...~<Disclosure N>~

The bytes of the digest MUST then be base64url encoded 🍓

The same hash algorithm as for the Disclosures MUST be used. That's defined by the _sd_alg element in the Issuer-signed JWT, or the default value as defined in Section 4.1.1 🧁

4.3.2. Validating the Key Binding JWT ✅

Whether to require Key Binding is up to the Verifier's policy, based on the set of trust requirements (like trust frameworks) it belongs to. See Section 9.5 for security considerations 🛡️

If the Verifier requires Key Binding, the Verifier MUST ensure that the key it uses to validate the signature on the Key Binding JWT is the key specified in the SD-JWT as the Holder's public key. For example, if the SD-JWT contains a cnf value with a jwk member, the Verifier would parse the provided JWK and use it to verify the Key Binding JWT 🔍

Details of the validation process are defined in Section 7.3 📚


5. Example SD-JWT 🎬

In this example, a simple SD-JWT is demonstrated. The example is split into issuance and presentation ✨

Note: Throughout the examples in this document, we added line breaks to JSON strings and base64-encoded strings so they fit the line-length limits in RFCs and are easier to read. JSON doesn't allow line breaks inside strings, though, so keep that in mind if you copy them 🙈

5.1. Issuance 🎀

okay bestie, so the Issuer starts with this user's whole profile 👀 this is the input JWT Claims Set it works from:

{
  "sub": "user_42",
  "given_name": "John",
  "family_name": "Doe",
  "email": "johndoe@example.com",
  "phone_number": "+1-202-555-0101",
  "phone_number_verified": true,
  "address": {
    "street_address": "123 Main St",
    "locality": "Anytown",
    "region": "Anystate",
    "country": "US"
  },
  "birthdate": "1940-01-01",
  "updated_at": 1570000000,
  "nationalities": [
    "US",
    "DE"
  ]
}

so the Issuer made some choices when building the SD-JWT, and they're giving main character energy ✨

  • The nationalities array is always visible, but its contents are selectively disclosable. 🌸
  • The sub element as well as essential verification data (iss, exp, cnf, etc.) are always visible. 💜
  • All other claims are selectively disclosable. 🦋
  • For address, the Issuer is using a flat structure, i.e., all the claims in the address claim can only be disclosed in full. Other options are discussed in Section 6. 🍓

next up is the payload for the SD-JWT. the _sd list is full of digests, and the ... entries under nationalities are the hashed array elements 🧚

{
  "_sd": [
    "CrQe7S5kqBAHt-nMYXgc6bdt2SH5aTY1sU_M-PgkjPI",
    "JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE",
    "PorFbpKuVu6xymJagvkFsFXAbRoc2JGlAUA2BA4o7cI",
    "TGf4oLbgwd5JQaHyKVQZU9UdGE0w5rtDsrZzfUaomLo",
    "XQ_3kPKt1XyX7KANkqVR6yZ2Va5NrPIvPYbyMvRKBMM",
    "XzFrzwscM6Gn6CJDc6vVK8BkMnfG8vOSKfpPIZdAfdE",
    "gbOsI4Edq2x2Kw-w5wPEzakob9hV1cRD0ATN3oQL9JM",
    "jsu9yVulwQQlhFlM_3JlzMaSFzglhQG0DpfayQwLUK4"
  ],
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "sub": "user_42",
  "nationalities": [
    {
      "...": "pFndjkZ_VCzmyTa6UjlZo3dh-ko8aIKQc9DlGzhaVYo"
    },
    {
      "...": "7Cf6JkPudry3lcbwHgeZ8khAv1U1OSlerP0VkBJrWZ0"
    }
  ],
  "_sd_alg": "sha-256",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc",
      "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"
    }
  }
}

now the Disclosures, which the Issuer made one per claim 💅 quick heads up: in this doc and everywhere else in the spec, the label "SHA-256 Hash:" is just shorthand for "Base64url-Encoded SHA-256 Hash:", so don't get confused when you see it 🙈

  • Claim given_name:
    • SHA-256 Hash: jsu9yVulwQQlhFlM_3JlzMaSFzglhQG0DpfayQwLUK4
    • Disclosure:
WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJ
d
  • Contents: ["2GLC42sKQveCfGfryNRN9w", "given_name", "John"]
  • Claim family_name:
    • SHA-256 Hash: TGf4oLbgwd5JQaHyKVQZU9UdGE0w5rtDsrZzfUaomLo
    • Disclosure:
WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJ
d
  • Contents: ["eluV5Og3gSNII8EYnsxA_A", "family_name", "Doe"]
  • Claim email:
    • SHA-256 Hash: JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE
    • Disclosure:
WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXh
hbXBsZS5jb20iXQ
  • Contents: ["6Ij7tM-a5iVPGboS5tmvVA", "email", "johndoe@example.com"]
  • Claim phone_number:
    • SHA-256 Hash: PorFbpKuVu6xymJagvkFsFXAbRoc2JGlAUA2BA4o7cI
    • Disclosure:
WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0
yMDItNTU1LTAxMDEiXQ
  • Contents: ["eI8ZWm9QnKPpNPeNenHdhQ", "phone_number", "+1-202-555-0101"]
  • Claim phone_number_verified:
    • SHA-256 Hash: XQ_3kPKt1XyX7KANkqVR6yZ2Va5NrPIvPYbyMvRKBMM
    • Disclosure:
WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInBob25lX251bWJlcl92ZXJpZml
lZCIsIHRydWVd
  • Contents: ["Qg_O64zqAxe412a108iroA", "phone_number_verified", true]
  • Claim address: (the flat one, all or nothing 🫶)
    • SHA-256 Hash: XzFrzwscM6Gn6CJDc6vVK8BkMnfG8vOSKfpPIZdAfdE
    • Disclosure:
WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9
hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLC
AicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0
  • Contents:
["AJx-095VPrpTtN4QMOqROA", "address", {"street_address": "123
Main St", "locality": "Anytown", "region": "Anystate",
"country": "US"}]
  • Claim birthdate:
    • SHA-256 Hash: gbOsI4Edq2x2Kw-w5wPEzakob9hV1cRD0ATN3oQL9JM
    • Disclosure:
WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImJpcnRoZGF0ZSIsICIxOTQwLTA
xLTAxIl0
  • Contents: ["Pc33JM2LchcU_lHggv_ufQ", "birthdate", "1940-01-01"]
  • Claim updated_at:
    • SHA-256 Hash: CrQe7S5kqBAHt-nMYXgc6bdt2SH5aTY1sU_M-PgkjPI
    • Disclosure:
WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDA
wMDAwXQ
  • Contents: ["G02NSrQfjFXQ7Io09syajA", "updated_at", 1570000000]
  • Array Entry: (nationalities, the first one 🇺🇸)
    • SHA-256 Hash: pFndjkZ_VCzmyTa6UjlZo3dh-ko8aIKQc9DlGzhaVYo
    • Disclosure:
WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIlVTIl0
  • Contents: ["lklxF5jMYlGTPUovMNIvCA", "US"]
  • Array Entry: (and the second one 🇩🇪)
    • SHA-256 Hash: 7Cf6JkPudry3lcbwHgeZ8khAv1U1OSlerP0VkBJrWZ0
    • Disclosure:
WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIkRFIl0
  • Contents: ["nPuoQnkRFq3BIeAm7AnXFA", "DE"]

then the Issuer signs the payload 🔐 and out comes the Issuer-signed JWT:

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBb
IkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZ
akg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBL
dVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1
SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tB
TmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2
Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFr
b2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpn
bGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUu
Y29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjog
InVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15
VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1
ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjog
InNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0y
NTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VH
ZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlG
MkhaUSJ9fX0.MczwjBFGtzf-6WMT-hIvYbkb11NrV1WMO-jTijpMPNbswNzZ87wY2uHz
-CXo6R04b7jYrpj9mNRAvVssXou1iw

last step: tack the Disclosures onto the JWT and you get the full SD-JWT 🦋✨

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBb
IkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZ
akg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBL
dVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1
SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tB
TmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2
Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFr
b2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpn
bGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUu
Y29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjog
InVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15
VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1
ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjog
InNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0y
NTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VH
ZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlG
MkhaUSJ9fX0.MczwjBFGtzf-6WMT-hIvYbkb11NrV1WMO-jTijpMPNbswNzZ87wY2uHz
-CXo6R04b7jYrpj9mNRAvVssXou1iw~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgI
mdpdmVuX25hbWUiLCAiSm9obiJd~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZh
bWlseV9uYW1lIiwgIkRvZSJd~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWl
sIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyJlSThaV205UW5LUHBOUGVOZW5IZGhR
IiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyJRZ19PNjR6cUF4Z
TQxMmExMDhpcm9BIiwgInBob25lX251bWJlcl92ZXJpZmllZCIsIHRydWVd~WyJBSngt
MDk1VlBycFR0TjRRTU9xUk9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjog
IjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFu
eXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0~WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZR
IiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJHMDJOU3JRZmpGWFE3SW8wOXN5
YWpBIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ~WyJsa2x4RjVqTVlsR1RQVW92T
U5JdkNBIiwgIlVTIl0~WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIkRFIl0~

5.2. Presentation ✨

Okay, here's a non-normative example of an SD-JWT+KB being sent from the Holder to the Verifier 💖 It has six tilde-separated parts 🎀: the Issuer-signed JWT at the very start, four Disclosures in the middle (for the claims given_name, family_name, address, and one of the nationalities 🌸), and the Key Binding JWT as the last element 🦋.

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBb
IkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZ
akg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBL
dVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1
SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tB
TmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2
Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFr
b2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpn
bGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUu
Y29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjog
InVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15
VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1
ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjog
InNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0y
NTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VH
ZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlG
MkhaUSJ9fX0.MczwjBFGtzf-6WMT-hIvYbkb11NrV1WMO-jTijpMPNbswNzZ87wY2uHz
-CXo6R04b7jYrpj9mNRAvVssXou1iw~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgI
mZhbWlseV9uYW1lIiwgIkRvZSJd~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImFk
ZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5
IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMi
fV0~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd
~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIlVTIl0~eyJhbGciOiAiRVMyNTYiLCA
idHlwIjogImtiK2p3dCJ9.eyJub25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodH
RwczovL3ZlcmlmaWVyLmV4YW1wbGUub3JnIiwgImlhdCI6IDE3NDg1MzcyNDQsICJzZF
9oYXNoIjogIjBfQWYtMkItRWhMV1g1eWRoX3cyeHp3bU82aU02NkJfMlFDRWFuSTRmVV
kifQ.T3SIus2OidNl41nmVkTZVCKKhOAX97aOldMyHFiYjHm261eLiJ1YiuONFiMN8Ql
CmYzDlBLAdPvrXh52KaLgUQ

The Holder created and signed the following Key Binding JWT payload for this presentation 🔐

{
  "nonce": "1234567890",
  "aud": "https://verifier.example.org",
  "iat": 1748537244,
  "sd_hash": "0_Af-2B-EhLWX5ydh_w2xzwmO6iM66B_2QCEanI4fUY"
}

If the Verifier doesn't require Key Binding, the Holder could just present the SD-JWT with the selected Disclosures directly, instead of wrapping it in an SD-JWT+KB 🙈

After validation, the Verifier gets the following Processed SD-JWT Payload to work with 🎉

{
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "sub": "user_42",
  "nationalities": [
    "US"
  ],
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc",
      "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"
    }
  },
  "family_name": "Doe",
  "address": {
    "street_address": "123 Main St",
    "locality": "Anytown",
    "region": "Anystate",
    "country": "US"
  },
  "given_name": "John"
}

6. Considerations on Nested Data in SD-JWTs ✨

Bestie, since it's JSON, an object in an SD-JWT payload MAY contain name/value pairs where the value is another object, or objects MAY be elements in arrays 🧚 In SD-JWT, the Issuer decides for each claim individually, on each level of the JSON, whether or not that claim should be selectively disclosable 💅 That choice can be made on each level independently of whether keys higher up in the hierarchy are selectively disclosable.

So it follows that the _sd key containing digests MAY appear multiple times in an SD-JWT, and likewise, there MAY be multiple arrays within the hierarchy, each with selectively disclosable elements 🌈 Digests of selectively disclosable claims MAY even appear within other Disclosures 🤯

The following examples show some of the options an Issuer has 💖 It's up to the Issuer to pick the structure, depending on things like the expected use cases for the SD-JWT, privacy requirements, size considerations, or operating environment requirements. For more examples with nested structures, check out Appendices A.1 and A.2 📚

Throughout this section, we're using the following input JWT Claims Set as our example 🎀

{
  "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
  "address": {
    "street_address": "Schulstr. 12",
    "locality": "Schulpforta",
    "region": "Sachsen-Anhalt",
    "country": "DE"
  }
}
Note: The following examples of the structures are non-normative and are not intended to represent all possible options. They are also not meant to define or restrict how the address claim can be represented in an SD-JWT.

6.1. Example: Flat SD-JWT

The Issuer can decide to treat the address claim as a block that's either disclosed completely or not at all 🍓 In this case, the entire address claim is treated as an object in the Disclosure, as shown below.

{
  "_sd": [
    "fOBUSQvo46yQO-wRwXBcGqvnbKIueISEL961_Sjd4do"
  ],
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
  "_sd_alg": "sha-256"
}

The Issuer would create the following Disclosure, referenced by the one hash in the SD-JWT ✨

  • Claim address:
    • SHA-256 Hash:
fOBUSQvo46yQO-wRwXBcGqvnbKIueISEL961_Sjd4do
  • Disclosure:
WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImFkZHJlc3MiLCB7InN0cmVldF9
hZGRyZXNzIjogIlNjaHVsc3RyLiAxMiIsICJsb2NhbGl0eSI6ICJTY2h1bHBmb3
J0YSIsICJyZWdpb24iOiAiU2FjaHNlbi1BbmhhbHQiLCAiY291bnRyeSI6ICJER
SJ9XQ
  • Contents:
["2GLC42sKQveCfGfryNRN9w", "address", {"street_address":
"Schulstr. 12", "locality": "Schulpforta", "region": "Sachsen-
Anhalt", "country": "DE"}]

6.2. Example: Structured SD-JWT

The Issuer may instead decide to make the contents of the address claim selectively disclosable individually 🦋

{
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
  "address": {
    "_sd": [
      "6vh9bq-zS4GKM_7GpggVbYzzu6oOGXrmNVGPHP75Ud0",
      "9gjVuXtdFROCgRrtNcGUXmF65rdezi_6Er_j76kmYyM",
      "KURDPh4ZC19-3tiz-Df39V8eidy1oV3a3H1Da2N0g88",
      "WN9r9dCBJ8HTCsS2jKASxTjEyW5m5x65_Z_2ro2jfXM"
    ]
  },
  "_sd_alg": "sha-256"
}

In this case, the Issuer would use the following data in the Disclosures for the address sub-claims 🧁

  • Claim street_address:
    • SHA-256 Hash:
9gjVuXtdFROCgRrtNcGUXmF65rdezi_6Er_j76kmYyM
  • Disclosure:
WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgInN0cmVldF9hZGRyZXNzIiwgIlN
jaHVsc3RyLiAxMiJd
  • Contents:
["2GLC42sKQveCfGfryNRN9w", "street_address", "Schulstr. 12"]
  • Claim locality:
    • SHA-256 Hash:
6vh9bq-zS4GKM_7GpggVbYzzu6oOGXrmNVGPHP75Ud0
  • Disclosure:
WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImxvY2FsaXR5IiwgIlNjaHVscGZ
vcnRhIl0
  • Contents:
["eluV5Og3gSNII8EYnsxA_A", "locality", "Schulpforta"]
  • Claim region:
    • SHA-256 Hash:
KURDPh4ZC19-3tiz-Df39V8eidy1oV3a3H1Da2N0g88
  • Disclosure:
WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgInJlZ2lvbiIsICJTYWNoc2VuLUF
uaGFsdCJd
  • Contents:
["6Ij7tM-a5iVPGboS5tmvVA", "region", "Sachsen-Anhalt"]
  • Claim country:
    • SHA-256 Hash:
WN9r9dCBJ8HTCsS2jKASxTjEyW5m5x65_Z_2ro2jfXM
  • Disclosure:
WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgImNvdW50cnkiLCAiREUiXQ
  • Contents:
["eI8ZWm9QnKPpNPeNenHdhQ", "country", "DE"]

The Issuer may also make one sub-claim of address permanently disclosed and hide only the other sub-claims 👀

{
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
  "address": {
    "_sd": [
      "6vh9bq-zS4GKM_7GpggVbYzzu6oOGXrmNVGPHP75Ud0",
      "9gjVuXtdFROCgRrtNcGUXmF65rdezi_6Er_j76kmYyM",
      "KURDPh4ZC19-3tiz-Df39V8eidy1oV3a3H1Da2N0g88"
    ],
    "country": "DE"
  },
  "_sd_alg": "sha-256"
}

In this case, there would be no Disclosure for country, since it's provided in the clear 🫶

6.3. Example: SD-JWT with Recursive Disclosures

The Issuer may also decide to make the contents of the address claim selectively disclosable recursively 🌀 That means the address claim is selectively disclosable itself, and so are its sub-claims 🔥

{
  "_sd": [
    "HvrKX6fPV0v9K_yCVFBiLFHsMaxcD_114Em6VT8x1lg"
  ],
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
  "_sd_alg": "sha-256"
}

The Issuer first creates Disclosures for the sub-claims, and then includes their digests in the Disclosure for the address claim 🧚

  • Claim street_address:
    • SHA-256 Hash:
9gjVuXtdFROCgRrtNcGUXmF65rdezi_6Er_j76kmYyM
  • Disclosure:
WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgInN0cmVldF9hZGRyZXNzIiwgIlN
jaHVsc3RyLiAxMiJd
  • Contents:
["2GLC42sKQveCfGfryNRN9w", "street_address", "Schulstr. 12"]
  • Claim locality:
    • SHA-256 Hash:
6vh9bq-zS4GKM_7GpggVbYzzu6oOGXrmNVGPHP75Ud0
  • Disclosure:
WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImxvY2FsaXR5IiwgIlNjaHVscGZ
vcnRhIl0
  • Contents:
["eluV5Og3gSNII8EYnsxA_A", "locality", "Schulpforta"]
  • Claim region:
    • SHA-256 Hash:
KURDPh4ZC19-3tiz-Df39V8eidy1oV3a3H1Da2N0g88
  • Disclosure:
WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgInJlZ2lvbiIsICJTYWNoc2VuLUF
uaGFsdCJd
  • Contents:
["6Ij7tM-a5iVPGboS5tmvVA", "region", "Sachsen-Anhalt"]
  • Claim country:
    • SHA-256 Hash:
WN9r9dCBJ8HTCsS2jKASxTjEyW5m5x65_Z_2ro2jfXM
  • Disclosure:
WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgImNvdW50cnkiLCAiREUiXQ
  • Contents:
["eI8ZWm9QnKPpNPeNenHdhQ", "country", "DE"]
  • Claim address:
    • SHA-256 Hash:
HvrKX6fPV0v9K_yCVFBiLFHsMaxcD_114Em6VT8x1lg
  • Disclosure:
WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImFkZHJlc3MiLCB7Il9zZCI6IFs
iNnZoOWJxLXpTNEdLTV83R3BnZ1ZiWXp6dTZvT0dYcm1OVkdQSFA3NVVkMCIsIC
I5Z2pWdVh0ZEZST0NnUnJ0TmNHVVhtRjY1cmRlemlfNkVyX2o3NmttWXlNIiwgI
ktVUkRQaDRaQzE5LTN0aXotRGYzOVY4ZWlkeTFvVjNhM0gxRGEyTjBnODgiLCAi
V045cjlkQ0JKOEhUQ3NTMmpLQVN4VGpFeVc1bTV4NjVfWl8ycm8yamZYTSJdfV0
  • Contents:
["Qg_O64zqAxe412a108iroA", "address", {"_sd": ["6vh9bq-
zS4GKM_7GpggVbYzzu6oOGXrmNVGPHP75Ud0",
"9gjVuXtdFROCgRrtNcGUXmF65rdezi_6Er_j76kmYyM", "KURDPh4ZC19-
3tiz-Df39V8eidy1oV3a3H1Da2N0g88",
"WN9r9dCBJ8HTCsS2jKASxTjEyW5m5x65_Z_2ro2jfXM"]}]

7. Verification and Processing ✨

7.1. Verification of the SD-JWT ✨

okay so, when a Holder or Verifier gets an SD-JWT (on its own or tucked inside an SD-JWT+KB 💅), they need to make sure two things are true:

  • the Issuer-signed JWT is valid, and
  • every Disclosure is valid and matches a digest value in the Issuer-signed JWT, either right in the payload or nested inside the contents of other Disclosures 🫶

The Holder or the Verifier MUST run these checks when they receive an SD-JWT, so they can validate it and pull out the payload 🔥

  1. Split the SD-JWT into the Issuer-signed JWT and the Disclosures (if there are any) 🎀
  2. Validate the Issuer-signed JWT ✨
    • a. Make sure the signing algorithm is one that counts as secure for the app. For the details, check [RFC8725], Sections 3.1 and 3.2. The "none" algorithm MUST NOT be accepted 🚫
    • b. Validate the signature over the Issuer-signed JWT per Section 5.2 of [RFC7515] 💜
    • c. Validate the Issuer, and make sure the signing key actually belongs to that Issuer 👑
    • d. Check that the _sd_alg claim value is one you understand, and that the hash algorithm is secure according to the Holder's or Verifier's policy (see Section 4.1.1) 🌸
  3. Process the Disclosures and the embedded digests in the Issuer-signed JWT like this 🧚
    • a. For each Disclosure you were given:
      • i. Calculate the digest over the base64url-encoded string, as described in Section 4.2.3 🍓
    • b. (*) Find all the embedded digests in the Issuer-signed JWT:
      • i. Find all objects that have an _sd key pointing to an array of strings.
      • ii. Find all array elements that are objects with exactly one key, where that key is ... and it refers to a string.
    • c. (**) For each embedded digest you found in the previous step: 🦋
      • i. Compare the value with the digests you calculated earlier and look for the matching Disclosure. If there's no such Disclosure, the digest MUST be ignored 🙈
      • ii. If the digest was found in an object's _sd key:
        1. If the contents of that Disclosure are not a JSON array of three elements (salt, claim name, claim value), the SD-JWT MUST be rejected 💀
        2. If the claim name is _sd or ..., the SD-JWT MUST be rejected 💀
        3. If the claim name already exists at the level of the _sd key, the SD-JWT MUST be rejected 💀
        4. At the level of the _sd key, insert a new claim using the claim name and claim value from the Disclosure ✨
        5. Recursively process the value using the steps described in (*) and (**) 🔁
      • iii. If the digest was found in an array element:
        1. If the contents of that Disclosure are not a JSON array of two elements (salt, value), the SD-JWT MUST be rejected 💀
        2. Replace the array element with the value from the Disclosure ✨
        3. Recursively process the value using the steps described in (*) and (**) 🔁
    • d. Remove every array element whose digest wasn't found in the previous step 🗑️
    • e. Remove all the _sd keys and their contents from the Issuer-signed JWT payload. If that leaves an object with no properties, it should be represented as an empty object {} 🫧
    • f. Remove the _sd_alg claim from the SD-JWT payload 🧹
  4. If any digest value shows up more than once in the Issuer-signed JWT payload (directly, or recursively through other Disclosures), the SD-JWT MUST be rejected 🙅‍♀️
  5. If any Disclosure wasn't referenced by a digest value in the Issuer-signed JWT (directly, or recursively through other Disclosures), the SD-JWT MUST be rejected 🙅‍♀️
  6. Check that the SD-JWT is valid using claims like nbf, exp, and aud in the processed payload, if they're there. If a required validity-controlling claim is missing (see Section 9.7), the SD-JWT MUST be rejected 💅

If any step fails, the SD-JWT is not valid, and processing MUST be aborted 🛑 If everything passes, the JSON document you end up with (the one this section calls the "Processed SD-JWT Payload") can be handed to the app to do its thing ✨

⚠️ bestie, real talk: these processing steps don't promise the Holder that they got a complete set of Disclosures. Some digest values in the Issuer-signed JWT (the ones that aren't decoys) might have no matching Disclosure, for example if the Issuer's message got cut off. It's up to the Holder to keep track of which Disclosures map to which plaintext claim values, so they can show them to the user when needed 🥺

7.2. Processing by the Holder ✨

The Issuer hands the Holder an SD-JWT, not an SD-JWT+KB. So if the Holder ever gets an SD-JWT+KB, it MUST be rejected 🙅‍♀️

When the Holder receives an SD-JWT, they MUST do the following 🫶

  1. Process the SD-JWT as defined in Section 7.1 to validate it and pull out the payload 🎀
  2. Make sure the claim contents in the payload are acceptable. This depends on the app, so for example, check whatever values the Holder is able to check are correct 🔍

Before presenting to a Verifier, the Holder MUST perform the following steps (or equivalent ones), on top of the checks from Section 7.1 that run after receiving the SD-JWT 💖

  1. Decide which Disclosures to release to the Verifier, getting consent if needed. (Heads up: how consent happens is out of scope for this document 🙈)
  2. Verify that each selected Disclosure meets one of these two conditions:
    • a. The hash of the Disclosure is contained in the Issuer-signed JWT claims.
    • b. The hash of the Disclosure is contained in the claim value of another selected Disclosure.
  3. Assemble the SD-JWT, with the Issuer-signed JWT and the selected Disclosures (see Section 4 for the format) 🧩
  4. If Key Binding is not required:
    • a. Send the SD-JWT to the Verifier 📨
  5. If Key Binding is required:
    • a. Create a Key Binding JWT tied to the SD-JWT 🔐
    • b. Assemble the SD-JWT+KB by concatenating the SD-JWT and the Key Binding JWT 🧷
    • c. Send the SD-JWT+KB to the Verifier 📨

7.3. Verification by the Verifier ✨

When a Verifier gets a presentation from a Holder, in either the SD-JWT or the SD-JWT+KB form, they also need to do the checks from Section 7.1. On top of that, they need to make sure:

  • if Key Binding is required, the Holder has provided an SD-JWT+KB, and
  • the Key Binding JWT is signed by the Holder and is valid 💜

To get there, Verifiers MUST follow these steps (or equivalent ones) 👑

  1. Decide whether Key Binding gets checked, based on the Verifier's policy for the use case at hand. This decision MUST NOT be based on whether or not the Holder sent a Key Binding JWT. Check Section 9.5 for the details 🧐
  2. If Key Binding is required and the Holder has provided an SD-JWT (without Key Binding), the Verifier MUST reject the presentation 🙅‍♀️
  3. If the Holder has provided an SD-JWT+KB, split it into an SD-JWT and a Key Binding JWT ✂️
  4. Process the SD-JWT as defined in Section 7.1 to validate the presentation and pull out the payload 🎀
  5. If Key Binding is required:
    • a. Figure out the Holder's public key from the SD-JWT (see Section 4.1.2) 🗝️
    • b. Make sure the signing algorithm is one that counts as secure for the app. For the details, check [RFC8725], Sections 3.1 and 3.2. The "none" algorithm MUST NOT be accepted 🚫
    • c. Validate the signature over the Key Binding JWT per Section 5.2 of [RFC7515] 💅
    • d. Check that the typ of the Key Binding JWT is kb+jwt (see Section 4.3) 🏷️
    • e. Check that the creation time of the Key Binding JWT, as set by the iat claim, falls within an acceptable window ⏰
    • f. Confirm that the Key Binding JWT is bound to the current transaction and was made for this Verifier (replay detection) by validating the nonce and aud claims 🛡️
    • g. Calculate the digest over the Issuer-signed JWT and Disclosures as defined in Section 4.3.1, and check that it matches the value of the sd_hash claim in the Key Binding JWT 🔍
    • h. Check that the Key Binding JWT is a valid JWT in every other way, per [RFC7519] and [RFC8725] ✨

If any step fails, the presentation is not valid, and processing MUST be aborted 🛑

Otherwise, the Processed SD-JWT Payload can be passed to the app to be used for its intended purpose 🦋

8. JWS JSON Serialization 🌈

This section describes an alternative format for SD-JWTs and SD-JWT+KBs that uses the JWS JSON Serialization from [RFC7515]. Supporting this format is OPTIONAL 🎀

8.1. New Unprotected Header Parameters ✨

For both the General and Flattened JSON Serialization, the SD-JWT or SD-JWT+KB is represented as a JSON object according to Section 7.2 of [RFC7515]. These new unprotected header parameters are defined:

  • disclosures: An array of strings, where each element is one individual Disclosure as described in Section 4.2 📦
  • kb_jwt: Only present in an SD-JWT+KB. This is the Key Binding JWT as described in Section 4.3 🔐

In an SD-JWT+KB, kb_jwt MUST be present when using the JWS JSON Serialization, and the digest in the sd_hash claim MUST be computed over the SD-JWT as described in Section 4.3.1. That means that even when you're using the JWS JSON Serialization, the regular SD-JWT Compact Serialization MUST be created temporarily so you can calculate the digest 🧮 To build the SD-JWT Compact Serialization part, you concatenate the protected header, the payload, and the signature of the JWS JSON serialized SD-JWT, using a . character as the separator, and use the Disclosures from the disclosures member of the unprotected header 🧩

👀 heads up: unprotected headers other than disclosures aren't covered by the digest. So, as usual, they aren't protected against tampering 😬

8.2. Flattened JSON Serialization ✨

Okay so with Flattened JSON Serialization 🦋 there is only one unprotected header, bestie. Simple and iconic.

Here's a non-normative example (vibes only, not a rule 🙈) of a JWS JSON serialized SD-JWT issued using Flattened JSON Serialization:

{
  "header": {
    "disclosures": [
      "WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgInN1YiIsICJqb2huX2RvZV80M
        iJd",
      "WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImdpdmVuX25hbWUiLCAiSm9ob
        iJd",
      "WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImZhbWlseV9uYW1lIiwgIkRvZ
        SJd",
      "WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgImJpcnRoZGF0ZSIsICIxOTQwL
        TAxLTAxIl0"
    ]
  },
  "payload": "eyJfc2QiOiBbIjRIQm42YUlZM1d0dUdHV1R4LXFVajZjZGs2V0JwWn
    lnbHRkRmF2UGE3TFkiLCAiOHNtMVFDZjAyMXBObkhBQ0k1c1A0bTRLWmd5Tk9PQV
    ljVGo5SE5hQzF3WSIsICJjZ0ZkaHFQbzgzeFlObEpmYWNhQ2FhN3VQOVJDUjUwVk
    U1UjRMQVE5aXFVIiwgImpNQ1hWei0tOWI4eDM3WWNvRGZYUWluencxd1pjY2NmRl
    JCQ0ZHcWRHMm8iXSwgImlzcyI6ICJodHRwczovL2lzc3Vlci5leGFtcGxlLmNvbS
    IsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgIl9zZF9hbG
    ciOiAic2hhLTI1NiIsICJjbmYiOiB7Imp3ayI6IHsia3R5IjogIkVDIiwgImNydi
    I6ICJQLTI1NiIsICJ4IjogIlRDQUVSMTladnUzT0hGNGo0VzR2ZlNWb0hJUDFJTG
    lsRGxzN3ZDZUdlbWMiLCAieSI6ICJaeGppV1diWk1RR0hWV0tWUTRoYlNJaXJzVm
    Z1ZWNDRTZ0NGpUOUYySFpRIn19fQ",
  "protected":
    "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0",
  "signature": "3oOtvPxU3QdDWUmfGexVB5rWyON2f1atg5rL825bvvD1g7ywjKDK
    y2UHqHoH2QS4FA99JbG5qnlqFaGXFChfjQ"
}

And here's an SD-JWT+KB with two Disclosures 🎀:

{
  "header": {
    "disclosures": [
      "WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImZhbWlseV9uYW1lIiwgIkRvZ
        SJd",
      "WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImdpdmVuX25hbWUiLCAiSm9ob
        iJd"
    ],
    "kb_jwt": "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dCJ9.eyJub25j
      ZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL3ZlcmlmaWVyLmV4YW
      1wbGUub3JnIiwgImlhdCI6IDE3NDg1MzcyNDQsICJzZF9oYXNoIjogIlZqdFBz
      Z1pwUVRSeEtKdkRwU0otblhsWktFOVo5TGdENEZ5Q3d3b05NUncifQ.GrDvJ2j
      hYNmUvqdwVEIrxeTFEuI5qKSM7I6P95JmA6Wko-FBB5vPGQn0wvmdgjLCE2iDR
      h1r82zchjmABQ3V8w"
  },
  "payload": "eyJfc2QiOiBbIjRIQm42YUlZM1d0dUdHV1R4LXFVajZjZGs2V0JwWn
    lnbHRkRmF2UGE3TFkiLCAiOHNtMVFDZjAyMXBObkhBQ0k1c1A0bTRLWmd5Tk9PQV
    ljVGo5SE5hQzF3WSIsICJjZ0ZkaHFQbzgzeFlObEpmYWNhQ2FhN3VQOVJDUjUwVk
    U1UjRMQVE5aXFVIiwgImpNQ1hWei0tOWI4eDM3WWNvRGZYUWluencxd1pjY2NmRl
    JCQ0ZHcWRHMm8iXSwgImlzcyI6ICJodHRwczovL2lzc3Vlci5leGFtcGxlLmNvbS
    IsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgIl9zZF9hbG
    ciOiAic2hhLTI1NiIsICJjbmYiOiB7Imp3ayI6IHsia3R5IjogIkVDIiwgImNydi
    I6ICJQLTI1NiIsICJ4IjogIlRDQUVSMTladnUzT0hGNGo0VzR2ZlNWb0hJUDFJTG
    lsRGxzN3ZDZUdlbWMiLCAieSI6ICJaeGppV1diWk1RR0hWV0tWUTRoYlNJaXJzVm
    Z1ZWNDRTZ0NGpUOUYySFpRIn19fQ",
  "protected":
    "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0",
  "signature": "3oOtvPxU3QdDWUmfGexVB5rWyON2f1atg5rL825bvvD1g7ywjKDK
    y2UHqHoH2QS4FA99JbG5qnlqFaGXFChfjQ"
}

8.3. General JSON Serialization 🌈

Okay this one is the main character of serializations 👑, because here there are multiple unprotected headers, one per signature. The rule is that if disclosures and kb_jwt are present, they MUST be included in the first unprotected header, and they MUST NOT be present in any following unprotected headers. Lowkey important, so don't mess it up 💅.

Here's a non-normative example (again, just a vibe 🧚) of a presentation of a JWS JSON serialized SD-JWT, including a Key Binding JWT, using General JSON Serialization:

{
  "payload": "eyJfc2QiOiBbIjRIQm42YUlZM1d0dUdHV1R4LXFVajZjZGs2V0JwWn
    lnbHRkRmF2UGE3TFkiLCAiOHNtMVFDZjAyMXBObkhBQ0k1c1A0bTRLWmd5Tk9PQV
    ljVGo5SE5hQzF3WSIsICJjZ0ZkaHFQbzgzeFlObEpmYWNhQ2FhN3VQOVJDUjUwVk
    U1UjRMQVE5aXFVIiwgImpNQ1hWei0tOWI4eDM3WWNvRGZYUWluencxd1pjY2NmRl
    JCQ0ZHcWRHMm8iXSwgImlzcyI6ICJodHRwczovL2lzc3Vlci5leGFtcGxlLmNvbS
    IsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgIl9zZF9hbG
    ciOiAic2hhLTI1NiIsICJjbmYiOiB7Imp3ayI6IHsia3R5IjogIkVDIiwgImNydi
    I6ICJQLTI1NiIsICJ4IjogIlRDQUVSMTladnUzT0hGNGo0VzR2ZlNWb0hJUDFJTG
    lsRGxzN3ZDZUdlbWMiLCAieSI6ICJaeGppV1diWk1RR0hWV0tWUTRoYlNJaXJzVm
    Z1ZWNDRTZ0NGpUOUYySFpRIn19fQ",
  "signatures": [
    {
      "header": {
        "disclosures": [
          "WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImZhbWlseV9uYW1lIiwgI
            kRvZSJd",
          "WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImdpdmVuX25hbWUiLCAiS
            m9obiJd"
        ],
        "kid": "issuer-key-1",
        "kb_jwt": "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dCJ9.eyJu
          b25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL3ZlcmlmaW
          VyLmV4YW1wbGUub3JnIiwgImlhdCI6IDE3NDg1MzcyNDQsICJzZF9oYXNo
          IjogInFieUlXUDNwaFZneEVzRFJpd2R3OVc2QkozZHhpUEx1bWNZcFBidT
          RFYjgifQ.VyZqxaVHh1XE6M-kuax_7Laq42uFDrx17lLG2jluyKgy_PqC8
          5z4DVpISdMZDdSANGs-0zN2N7xnM-E1Pg0sOw"
      },
      "protected":
        "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0",
      "signature": "dz1N3uvhVHJjldyXwppmBLieTj0vuBMbzL06rnrLIuxEQb9B
        HoIOwGrWh-UadW4orRpEiEtjf7xyHDONMJ6tBw"
    },
    {
      "header": {
        "kid": "issuer-key-2"
      },
      "protected":
        "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0",
      "signature": "kuXio_U88RH_-fihAPET4AFUjj0BpxsT6yddMFIr6pfHKtAe
        0FOJNWQxU42rfnORuNQNTgGsf2A8LjEba5inNg"
    }
  ]
}

8.4. Verification of the JWS JSON Serialized SD-JWT 🔍

Verifying a JWS JSON serialized SD-JWT follows the rules from Section 3.4, with a few tweaks 🧁:

  • The SD-JWT or SD-JWT+KB doesn't need to be split into its component parts, and the Disclosures can be found in the disclosures member of the unprotected header.
  • To verify the digest in sd_hash in the Key Binding JWT of an SD-JWT+KB, the Verifier MUST assemble the string to be hashed as described in Section 8.1.

9. Security Considerations 🛡️

Okay, this part is seriously important, bestie 😭. The security considerations help achieve the following properties:

Selective Disclosure: An adversary playing the Verifier can't learn anything from an SD-JWT about any claim name or claim value that the Holder didn't explicitly disclose, unless that info can be derived from other disclosed claims or from sources outside the presented SD-JWT. 🕵️

Integrity: A malicious Holder can't change the names or values of selectively disclosable claims without the Verifier noticing. 🚨

And as described in Section 9.5, applying Key Binding can make sure that the presenter of an SD-JWT credential is actually the Holder of that credential. 🔑

9.1. Mandatory Signing of the Issuer-Signed JWT 🖊️

The JWT MUST be signed by the Issuer to protect the integrity of the issued claims. Otherwise an attacker can modify or add claims, like flipping the "email" attribute to take over the victim's account or sneaking in a fake academic qualification. 😱

The Verifier MUST always check the signature of the Issuer-signed JWT to make sure it hasn't been tampered with since it was issued. The Issuer-signed JWT MUST be rejected if the signature cannot be verified. 🙅

The security of the Issuer-signed JWT depends on the security of the signature algorithm. Per the last paragraph of Section 5.2 of [RFC7515], it's an application-specific decision to choose the appropriate JWS algorithm from [JWS.Algs], including post-quantum algorithms, when they are ready. 🧠

9.2. Manipulation of Disclosures 🙈

Holders can manipulate the Disclosures by changing the values of the claims before sending them to the Verifier. So the Verifier MUST check the Disclosures to make sure the values of the claims are correct, i.e., the digests of the Disclosures are actually present in the signed SD-JWT. 🔍

A naive Verifier that pulls all claim values out of the Disclosures (without checking the hashes) and shoves them into the SD-JWT payload is vulnerable to this attack. Plus, in a structured SD-JWT, without comparing the digests of the Disclosures, such an implementation couldn't even figure out the right place in a nested object to insert a claim. So the naive implementation is not only insecure, but also incorrect. 💀

The steps described in Section 7.3 make sure the Verifier checks the Disclosures correctly. ✅

9.3. Entropy of the Salt 🧂

The security model that conceals the plaintext claims relies on high-entropy random data in the salt as additional input to the hash function. The randomness means the same plaintext claim value doesn't produce the same digest value. It also makes it infeasible to guess the preimage of the digest (and therefore learn the plaintext claim value) by plugging potential values for a claim into the hash function and searching for a matching digest. It's therefore vitally important that unrevealed salts can't be learned or guessed, even if other salts have been revealed. 🔐 Each salt MUST be created in such a manner that it is cryptographically random, sufficiently long, and has high enough entropy that it is infeasible to guess. A new salt MUST be chosen for each claim independently of other salts. See "Randomness Requirements for Security" [RFC4086] for considerations on generating random values. 🎲

The RECOMMENDED minimum length of the randomly generated portion of the salt is 128 bits. 📏

The Issuer MUST ensure that a new salt value is chosen for each claim, including when the same claim name occurs at different places in the structure of the SD-JWT. You can see this in the example in Appendix A.2, where multiple claims with the name type appear, but each of them has a different salt. 🎀

9.4. Choice of a Hash Algorithm 🧁

To keep the privacy of claims that are selectively disclosable but aren't being disclosed in a given presentation, the hash function MUST ensure that it is infeasible to calculate any portion of the three elements (salt, claim name, claim value) from a particular digest. This implies the hash function MUST be preimage resistant and should also not allow an observer to infer any partial information about the undisclosed content. In the terminology of cryptographic commitment schemes, the hash function needs to be computationally hiding. 🫣

To ensure the integrity of selectively disclosable claims, the hash function MUST be second-preimage resistant. That is, for any combination of salt, claim name, and claim value, it is infeasible to find a different combination of salt, claim name, and claim value that results in the same digest. 🔒

The hash function SHOULD also be collision resistant. Although not essential to the anticipated uses of SD-JWT, without collision resistance an Issuer may be able to find multiple Disclosures that have the same hash value. In which case, the signature over the SD-JWT would not then commit the Issuer to the contents of the JWT. The collision resistance of the hash function used to generate digests SHOULD match the collision resistance of the hash function used by the signature scheme. For example, use of the ES512 signature algorithm would require a Disclosure hash function with at least 256-bit collision resistance, such as SHA-512. 💪

Inclusion in the "Named Information Hash Algorithm Registry" [Hash.Algs] alone does not indicate a hash algorithm's suitability for use in SD-JWT. It contains several heavily truncated digests, such as sha-256-32 and sha-256-64, which are unfit for security applications. 🙅‍♀️

9.5. Key Binding 🔐

Okay so Key Binding is all about making sure the person showing the credential is actually the Holder 🫶. An SD-JWT that supports Key Binding has a public key (or a reference to one) that matches a private key the Holder has. The Verifier needs the Holder to prove they've got that private key when they present the SD-JWT credential 🔑.

Without Key Binding, the Verifier only learns that the credential was issued by a certain Issuer 💅, but ANYONE who gets their hands on it can replay it 😭. So if it leaks to an attacker, they can show it to any Verifier that doesn't require a binding 💀. And a sketchy Verifier that the Holder showed it to can pass it along to another Verifier that doesn't require Key Binding either 🙈. Not okay!

Verifiers MUST decide whether Key Binding is required for a particular use case before verifying a credential ✨. Lots of things can factor into that decision: business requirements, the use case, what kind of binding between a Holder and their credential that use case needs, how sensitive the use case is, what properties the credential is expected to have, what other credentials might get presented alongside it, and so on 🌈.

It's giving security-policy realness. A Verifier should not make its security decisions based on data that an attacker can influence 🫣. For this reason, when deciding whether Key Binding is required, Verifiers MUST NOT take into account whether the Holder provided an SD-JWT+KB or a bare SD-JWT. Otherwise, an attacker could strip the KB-JWT from an SD-JWT+KB and present the leftover SD-JWT 💀.

Furthermore, Verifiers should be aware that Key Binding information may have been added to an SD-JWT in a format they don't recognize, so they might not be able to tell whether the SD-JWT supports Key Binding at all 🤷‍♀️.

If a Verifier decides that Key Binding is required for a particular use case, and the Holder presents either a bare SD-JWT or an SD-JWT+KB with an invalid Key Binding JWT, then the Verifier will reject the presentation when following the verification steps described in Section 7.3 🚫. Bye bestie.


9.6. Concealing Claim Names 🫣

SD-JWT makes sure the names of claims that are selectively disclosable are always concealed unless the claim's value is disclosed 🤫. This keeps an attacker from learning the names of those claims. BUT the names of the claims that are permanently disclosed are NOT hidden 👀. That includes the keys of objects that aren't concealed themselves but contain concealed claims. Issuers need to keep this limitation in mind when they create the structure of the SD-JWT 📝.


9.7. Selectively Disclosable Validity Claims ⏰

An Issuer MUST NOT allow any content to be selectively disclosable that is critical for evaluating the SD-JWT's authenticity or validity 🚨. The exact list depends on the application, and it SHOULD be listed by any application-specific profiles of SD-JWT. These registered JWT claim names SHOULD be considered security critical:

  • iss (Issuer)
  • aud (Audience), although issuers MAY allow individual entries in the array to be selectively disclosable
  • exp (Expiration Time)
  • nbf (Not Before)
  • cnf (Confirmation Key)

Issuers will typically put the claims that control an SD-JWT's validity in plaintext in the payload 📄, but there's NO guarantee they will. So Verifiers can't rely on that 🙅‍♀️ and need to operate as though security-critical claims might be selectively disclosable.

Verifiers therefore MUST ensure that all claims they need for checking an SD-JWT's validity in the given context are present (or disclosed, respectively) during validation ✅. This gets done in the last step of the verification defined in Section 7.1.

The precise set of required validity claims is usually defined by operating environment rules, an application-specific profile, or the credential format 🗂️, and it MAY include claims other than the ones listed here. ✨


9.8. Distribution and Rotation of Issuer Signature Verification Key 🗝️

This specification does NOT define how signature verification keys of Issuers get distributed to Verifiers 🙃. However, it is RECOMMENDED that Issuers publish their keys in a way that allows for efficient and secure key rotation and revocation. One example is publishing keys at a predefined location using the JSON Web Key Set (JWKS) format [RFC7517] 🌸. Verifiers need to make sure they are not using expired or revoked keys for signature verification, using reasonable and appropriate means for the given key-distribution method 🔍.


9.9. Forwarding Credentials 📨

Any entity holding an SD-JWT (including an SD-JWT extracted from an SD-JWT+KB) can forward it to any third party that does not enforce Key Binding 💌. When they do, that entity may remove Disclosures so the receiver only learns a subset of the claims in the original SD-JWT 🧩.

For example, a device manufacturer might produce an SD-JWT with info about upstream and downstream supply chain contributors 🏭. Each supply chain party can verify only the claims an upstream party selectively disclosed to them, and they can choose to reduce the disclosed claims even further when presenting to a downstream party 🔗.

Honestly this behaviour could be desirable in some scenarios 🥰. If it's NOT desirable, then Issuers need to support Key Binding and Verifiers need to enforce it 🔒.


9.10. Integrity of SD-JWTs and SD-JWT+KBs 🛡️

With an SD-JWT, the Issuer-signed JWT is integrity protected by the Issuer's signature, and the values of the Disclosures are integrity protected by the digests included in it 💖. The specific set of Disclosures, however, is NOT integrity protected 😬. That means the SD-JWT can be modified by adding or removing Disclosures and still be valid.

With an SD-JWT+KB, the set of selected Disclosures IS integrity protected 🎀. The signature in the Key Binding JWT covers a specific SD-JWT, with a specific Issuer-signed JWT and a specific set of Disclosures. So the signature on the Key Binding JWT doesn't just prove Key Binding, it also assures the authenticity and integrity of the set of Disclosures the Holder disclosed ✨. The set of Disclosures in an SD-JWT+KB is the set the Holder intended to send, and no intermediate party has added, removed, or modified the list of Disclosures 🙌.


9.11. Explicit Typing 🏷️

Section 3.11 of [RFC8725] describes explicit typing as one mechanism to prevent confusion attacks (described in Section 2.8 of [RFC8725]), where one kind of JWT gets mistaken for another 🤯. SD-JWTs can be subject to these confusion attacks too! So in the absence of other techniques, it is RECOMMENDED that application profiles of SD-JWT specify an explicit type by including the typ header parameter when the SD-JWT is issued, and that Verifiers check this value 📛.

When explicit typing using the typ header is used for an SD-JWT, it is RECOMMENDED that a media type name of the format "application/example+sd-jwt" be used, where example is replaced by the identifier for the specific kind of SD-JWT 🎟️. The definition of typ in Section 4.1.9 of [RFC7515] recommends that the "application/" prefix be omitted, so "example+sd-jwt" would be the value of the typ header parameter 💅.

Using the cty content type header parameter to indicate the content type of the SD-JWT payload can also help distinguish different types of JSON objects or different kinds of JWT Claim Sets 🧁.


9.12. Key Pair Generation and Lifecycle Management 🔑✨

Implementations of SD-JWT rely on asymmetric cryptographic keys, so they must make sure that key pair generation, handling, storage, and lifecycle management are all done securely 🔐.

The specific mechanisms for secure key management are out of scope for this document 🙈, but implementers should follow established best practices, like the ones outlined in NIST SP 800-57 Part 1 [NIST.SP.800-57pt1r5]. This includes:

  • Secure Generation: Using cryptographically secure methods and random number generators 🎲.
  • Secure Storage: Protecting private keys from unauthorized access 🏰.
  • Lifecycle Management: Ensuring secure key rotation, revocation, and disposal as needed ♻️.

Appropriate key management is essential, because any compromise can lead to unauthorized disclosure or forgery of SD-JWTs 😭💀. Bestie, don't let that happen!


10. Privacy Considerations 🦋

10. Privacy Considerations ✨

10.1. Unlinkability 🦋

okay so unlinkability is basically the property where bad actors are stopped from connecting the dots between a user's credential presentations, beyond what the user actually agreed to. 🫶 without it, an adversary could end up learning way more about the user than they ever meant to share 😭 like:

  • Cooperating Verifiers might want to track users across services to build advertising profiles. 💀
  • Issuers might want to track where users present their credentials to enable surveillance. 🙈
  • After a data breach at multiple Verifiers, publicly available information might allow linking identifiable information presented to Verifier A with originally anonymous information presented to Verifier B, therefore revealing the identities of users of Verifier B. 🔥

the following types of unlinkability are discussed below: 💅

  • Presentation Unlinkability: A Verifier should not be able to link two presentations of the same credential. 🌸
  • Verifier/Verifier Unlinkability: The presentations made to two different Verifiers should not reveal that the same credential was presented (e.g., if the two Verifiers collude, or if they are forced by a third party to reveal the presentations made to them, or data leaks from one Verifier to the other). 🧚
  • Issuer/Verifier Unlinkability (Honest Verifier): An Issuer of a credential should not be able to know that a user presented this credential unless the Verifier is sharing presentation data with the Issuer accidentally, deliberately, or because it is forced to do so. 🦋
  • Issuer/Verifier Unlinkability (Careless/Colluding/Compromised/Coerced Verifier): >An Issuer of a credential should under no circumstances be able to tell that a user presented this credential to a certain Verifier. In particular, this includes cases when the Verifier accidentally or deliberately shares presentation data with the Issuer or is forced to do so. 👑

In all cases, unlinkability is limited to cases where the disclosed claims do not contain information that directly or indirectly identifies the user. 🎀 For example, when a taxpayer identification number is contained in the disclosed claims, the Issuer and Verifier can easily link the user's transactions. ngl that's a big yikes 💀 However, when the user only discloses a birthdate to one Verifier and a postal code to another Verifier, the two Verifiers should not be able to determine that they were interacting with the same user. ✨

Issuer/Verifier unlinkability with a careless, colluding, compromised, or coerced Verifier cannot be achieved in salted hash-based selective disclosure approaches, such as SD-JWT, as the issued credential with the Issuer's signature is directly presented to the Verifier, who can forward it to the Issuer. 😭 To reduce the risk of revealing the data later on, Section 10.2 defines requirements to reduce the amount of data stored. 🫶

In considering Issuer/Verifier unlinkability, it is important to note the potential for an asymmetric power dynamic between Issuers and Verifiers. 👀 This dynamic can compel an otherwise Honest Verifier into collusion. For example, a governmental Issuer might have the authority to mandate that a Verifier report back information about the credentials presented to it. Legal requirements could further enforce this, explicitly undermining Issuer/Verifier unlinkability. Similarly, a large service provider issuing credentials might implicitly pressure Verifiers into collusion by incentivizing participation in their larger operating environment. Deployers of SD-JWT must be aware of these potential power dynamics, mitigate them as much as possible, and/or make the risks transparent to the user. 🔥

Contrary to that, Issuer/Verifier unlinkability with an Honest Verifier can generally be achieved. ✨ However, a callback from the Verifier to the Issuer, such as a revocation check, could potentially disclose information about the credential's usage to the Issuer. Where such callbacks are necessary, they need to be executed in a manner that preserves privacy and does not disclose details about the credential to the Issuer (the mechanism described in [TSL] is an example of an approach that discloses minimal information towards the Issuer). It is important to note that the timing of such requests could potentially serve as a side channel. 🙈

Verifier/Verifier unlinkability and presentation unlinkability can be achieved using batch issuance: A batch of credentials based on the same claims is issued to the Holder instead of just a single credential. 🌈 The Holder can then use a different credential for each Verifier or even for each session with a Verifier. New Key Binding keys and salts MUST be used for each credential in the batch to ensure that the Verifiers cannot link the credentials using these values. Likewise, claims carrying time information, like iat, exp, and nbf, MUST either be randomized within a time period considered appropriate (e.g., randomize iat within the last 24 hours and calculate exp accordingly) or rounded (e.g., rounded down to the beginning of the day). 💖

SD-JWT only conceals the value of claims that are not revealed. 💅 It does not meet the security properties for anonymous credentials [CL01]. In particular, colluding Verifiers and Issuers can know when they have seen the same credential no matter what fields have been disclosed, even when none have been disclosed. This behavior may not align with what users naturally anticipate or are guided to expect from user-interface interactions, potentially causing them to make decisions they might not otherwise make. 😭 Workarounds such as batch issuance, as described above, help with keeping Verifiers from linking different presentations, but cannot work for Issuer/Verifier unlinkability. This issue applies to all salted hash-based approaches, including mDL/mDoc [ISO.18013-5] and SD-CWT [SD-CWT]. 🧚

10.2. Storage of User Data 💜

wherever user data is stored, it's a potential target for an attacker 👀 and it gets extra juicy when the data is signed by a trusted authority like an official national identity service. 👑 For example, in OpenID Connect [OpenID.Core], signed ID Tokens can be stored by Relying Parties. In the case of SD-JWT, Holders have to store SD-JWTs, and Issuers and Verifiers may decide to do so as well. 🌸

not surprisingly, a leak of such data risks revealing private data of users to third parties. 💀 Signed user data, the authenticity of which can be easily verified by third parties, further exacerbates the risk. As discussed in Section 9.5, leaked SD-JWTs may also allow attackers to impersonate Holders unless Key Binding is enforced and the attacker does not have access to the Holder's cryptographic keys. 🚨

due to these risks, and the risks described in Section 10.1, systems implementing SD-JWT SHOULD be designed to minimize the amount of data that is stored. All involved parties SHOULD NOT store SD-JWTs longer than strictly necessary, including in log files. 🍓

after Issuance, Issuers SHOULD NOT store the Issuer-signed JWT or the respective Disclosures. 🫶

Holders SHOULD store SD-JWTs only in encrypted form, and, wherever possible, use hardware-backed encryption in particular for the private Key Binding key. Decentralized storage of data, e.g., on user devices, SHOULD be preferred for user credentials over centralized storage. Expired SD-JWTs SHOULD be deleted as soon as possible. 🔐✨

after Verification, Verifiers SHOULD NOT store the Issuer-signed JWT or the respective Disclosures. It may be sufficient to store the result of the verification and any user data that is needed for the application. 🦋

Exceptions from the rules above can be made if there are strong requirements to do so (e.g., functional requirements or legal audit requirements), secure storage can be ensured, and the privacy impact has been assessed. 🎀

10.3. Confidentiality During Transport 🌈

if an SD-JWT or SD-JWT+KB is sent over an insecure channel during issuance or presentation, an adversary may be able to intercept and read the user's personal data or correlate the information with previous uses. 😱

usually, transport protocols for issuance and presentation of credentials are designed to protect the confidentiality of the transmitted data, for example, by requiring the use of TLS. 🔒

this specification therefore considers the confidentiality of the data to be provided by the transport protocol and does not specify any encryption mechanism. 💅

Implementers MUST ensure that the transport protocol provides confidentiality if the privacy of user data or correlation attacks by passive observers are a concern. 🚨

to encrypt an SD-JWT or SD-JWT+KB during transit over potentially insecure or leakage-prone channels, implementers MAY use JSON Web Encryption (JWE) [RFC7516], encapsulating the SD-JWT or SD-JWT+KB as the plaintext payload of the JWE. Especially, when an SD-JWT is transmitted via a URL and information may be stored/cached in the browser or end up in web server logs, the SD-JWT SHOULD be encrypted using JWE. 🧚

10.4. Decoy Digests 🎭

the use of decoy digests is RECOMMENDED when the number of claims (or the existence of particular claims) can be a side channel disclosing information about otherwise undisclosed claims. 🙈 In particular, if a claim in an SD-JWT is present only if a certain condition is met (e.g., a membership number is only contained if the user is a member of a group), the Issuer SHOULD add decoy digests when the condition is not met. 🌸

decoy digests increase the size of the SD-JWT. The number of decoy digests (or whether to use them at all) is a trade-off between the size of the SD-JWT and the privacy of the user's data. ⚖️✨

10.5. Issuer Identifier 🪪

an Issuer issuing only one type of SD-JWT might have privacy implications, because if the Holder has an SD-JWT issued by that Issuer, its type and claim names can be determined. 👀

for example, if a cancer research institute only issued SD-JWTs with cancer registry information, it is possible to deduce that the Holder owning its SD-JWT is a cancer patient. 😭

moreover, the Issuer identifier alone may reveal information about the user. 💀

for example, when a military organization or a drug rehabilitation center issues a vaccine credential, Verifiers can deduce that the Holder is a military member or may have a substance use disorder. 😬

to mitigate this issue, a group of issuers may elect to use a common Issuer identifier. A group signature scheme outside the scope of this specification may also be used, instead of an individual signature. 🫶

11. IANA Considerations 📋

11.1. JSON Web Token Claims Registration ✨

okay so IANA registered four new claims in the "JSON Web Token Claims" registry [JWT.Claims] that [RFC7519] set up 💅 here they are, bestie:

Claim Name:  _sd
Claim Description:  Digests of Disclosures for object properties
Change Controller:  IETF
Specification Document(s):  Section 4.2.4.1 of RFC 9901

Claim Name:  ...
Claim Description:  Digest of the Disclosure for an array element
Change Controller:  IETF
Specification Document(s):  Section 4.2.4.2 of RFC 9901

Claim Name:  _sd_alg
Claim Description:  Hash algorithm used to generate Disclosure
   digests and digest over presentation
Change Controller:  IETF
Specification Document(s):  Section 4.1.1 of RFC 9901

Claim Name:  sd_hash
Claim Description:  Digest of the SD-JWT to which the KB-JWT is tied
Change Controller:  IETF
Specification Document(s):  Section 4.3 of RFC 9901

the ... claim name is literally just three dots for array elements, iykyk 🙈

11.2. Media Type Registrations 🎀

IANA registered the following media types [RFC2046] in the "Media Types" registry [MediaTypes], following the process in [RFC6838]. Each one has its own template below.

Note: For the media type value used in the typ header in the Issuer-signed JWT itself, see Section 9.11.

11.2.1. SD-JWT Content 🦋

this one is for when the content is an SD-JWT:

Type name:  application

Subtype name:  sd-jwt

Required parameters:  n/a

Optional parameters:  n/a

Encoding considerations:  binary; application/sd-jwt values are a
   series of base64url-encoded values (some of which may be the empty
   string) separated by period ('.') and tilde ('~') characters.

Security considerations:  See the Security Considerations sections of
   RFC 9901, [RFC7519], and [RFC8725].

Interoperability considerations:  n/a

Published specification:  RFC 9901

Applications that use this media type:  Applications requiring
   selective disclosure of integrity-protected content.

Fragment identifier considerations:  n/a

Additional information:

   Magic number(s):  n/a
   File extension(s):  n/a
   Macintosh file type code(s):  n/a

Person & email address to contact for further information:  Daniel
   Fett, mail@danielfett.de

Intended usage:  COMMON

Restrictions on usage:  none

Author:  Daniel Fett, mail@danielfett.de

Change Controller:  IETF

11.2.2. JWS JSON Serialized SD-JWT Content 🌸

this one is for when the content is a JWS JSON serialized SD-JWT:

Type name:  application

Subtype name:  sd-jwt+json

Required parameters:  n/a

Optional parameters:  n/a

Encoding considerations:  binary; application/sd-jwt+json values are
   represented as a JSON Object.

Security considerations:  See the Security Considerations sections of
   RFC 9901 and [RFC7515].

Interoperability considerations:  n/a

Published specification:  RFC 9901

Applications that use this media type:  Applications requiring
   selective disclosure of content protected by ETSI JAdES compliant
   signatures.

Fragment identifier considerations:  n/a

Additional information:

   Magic number(s):  n/a
   File extension(s):  n/a
   Macintosh file type code(s):  n/a

Person & email address to contact for further information:  Daniel
   Fett, mail@danielfett.de

Intended usage:  COMMON

Restrictions on usage:  none

Author:  Daniel Fett, mail@danielfett.de

Change Controller:  IETF

11.2.3. Key Binding JWT Content 🫶

and this one is for when the content is a Key Binding JWT:

Type name:  application

Subtype name:  kb+jwt

Required parameters:  n/a

Optional parameters:  n/a

Encoding considerations:  binary; A Key Binding JWT is a JWT; JWT
   values are encoded as a series of base64url-encoded values
   separated by period ('.') characters.

Security considerations:  See the Security Considerations sections of
   RFC 9901, [RFC7519], and [RFC8725].

Interoperability considerations:  n/a

Published specification:  RFC 9901

Applications that use this media type:  Applications utilizing a JWT-
   based proof-of-possession mechanism.

Fragment identifier considerations:  n/a

Additional information:

   Magic number(s):  n/a
   File extension(s):  n/a
   Macintosh file type code(s):  n/a

Person & email address to contact for further information:  Daniel
   Fett, mail@danielfett.de

Intended usage:  COMMON

Restrictions on usage:  none

Author:  Daniel Fett, mail@danielfett.de

Change Controller:  IETF

11.3. Structured Syntax Suffixes Registration 🧚

IANA registered "+sd-jwt" in the "Structured Syntax Suffixes" registry [StructuredSuffix] using the process in [RFC6838]. It can be used to show that a media type is encoded as an SD-JWT. Here's the entry:

Name:  SD-JWT

+suffix:  +sd-jwt

References:  RFC 9901

Encoding considerations:  binary; SD-JWT values are a series of
   base64url-encoded values (some of which may be the empty string)
   separated by period ('.') or tilde ('~') characters.

Interoperability considerations:  n/a

Fragment identifier considerations:  n/a

Security considerations:  See the Security Considerations sections of
   RFC 9901, [RFC7519], and [RFC8725].

Contact:  Daniel Fett, mail@danielfett.de

Author/Change controller:  IETF

12. References 📚

12.1. Normative References 💜

[RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
           Requirement Levels", BCP 14, RFC 2119,
           DOI 10.17487/RFC2119, March 1997,
           <https://www.rfc-editor.org/info/rfc2119>.

[RFC5234]  Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax
           Specifications: ABNF", STD 68, RFC 5234,
           DOI 10.17487/RFC5234, January 2008,
           <https://www.rfc-editor.org/info/rfc5234>.

[RFC6838]  Freed, N., Klensin, J., and T. Hansen, "Media Type
           Specifications and Registration Procedures", BCP 13,
           RFC 6838, DOI 10.17487/RFC6838, January 2013,
           <https://www.rfc-editor.org/info/rfc6838>.

[RFC7515]  Jones, M., Bradley, J., and N. Sakimura, "JSON Web
           Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May
           2015, <https://www.rfc-editor.org/info/rfc7515>.

[RFC7516]  Jones, M. and J. Hildebrand, "JSON Web Encryption (JWE)",
           RFC 7516, DOI 10.17487/RFC7516, May 2015,
           <https://www.rfc-editor.org/info/rfc7516>.

[RFC7519]  Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token
           (JWT)", RFC 7519, DOI 10.17487/RFC7519, May 2015,
           <https://www.rfc-editor.org/info/rfc7519>.

[RFC7800]  Jones, M., Bradley, J., and H. Tschofenig, "Proof-of-
           Possession Key Semantics for JSON Web Tokens (JWTs)",
           RFC 7800, DOI 10.17487/RFC7800, April 2016,
           <https://www.rfc-editor.org/info/rfc7800>.

[RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
           2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
           May 2017, <https://www.rfc-editor.org/info/rfc8174>.

[RFC8725]  Sheffer, Y., Hardt, D., and M. Jones, "JSON Web Token Best
           Current Practices", BCP 225, RFC 8725,
           DOI 10.17487/RFC8725, February 2020,
           <https://www.rfc-editor.org/info/rfc8725>.

12.2. Informative References 🔍

[CL01]     Camenisch, J. and A. Lysyanskaya, "An Efficient System for
           Non-Transferable Anonymous Credentials with Optional
           Anonymity Revocation", Cryptology ePrint Archive, Paper
           2001/019, 2001, <https://eprint.iacr.org/2001/019.pdf>.

[Hash.Algs]
           IANA, "Named Information Hash Algorithm Registry",
           <https://www.iana.org/assignments/named-information>.

[ISO.18013-5]
           ISO/IEC, "Personal identification - ISO-compliant driving
           license - Part 5: Mobile driving license (mDL)
           application", ISO/IEC 18013-5:2021, September 2021,
           <https://www.iso.org/standard/69084.html>.

[JWS.Algs] IANA, "JSON Web Signature and Encryption Algorithms",
           <https://www.iana.org/assignments/jose>.

[JWT.Claims]
           IANA, "JSON Web Token Claims",
           <https://www.iana.org/assignments/jwt>.

[MediaTypes]
           IANA, "Media Types",
           <https://www.iana.org/assignments/media-types>.

[NIST.SP.800-57pt1r5]
           Barker, E., "Recommendation for Key Management: Part 1 -
           General", NIST SP 800-57pt1r5,
           DOI 10.6028/NIST.SP.800-57pt1r5, May 2020,
           <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/
           NIST.SP.800-57pt1r5.pdf>.

[OIDC.IDA] Lodderstedt, T., Fett, D., Haine, M., Pulido, A., Lehmann,
           K., and K. Koiwai, "OpenID Connect for Identity Assurance
           1.0", 1 October 2024, <https://openid.net/specs/openid-
           connect-4-identity-assurance-1_0.html>.

[OpenID.Core]
           Sakimura, N., Bradley, J., Jones, M., de Medeiros, B., and
           C. Mortimore, "OpenID Connect Core 1.0 incorporating
           errata set 2", 15 December 2023,
           <https://openid.net/specs/openid-connect-core-1_0.html>.

[RFC2046]  Freed, N. and N. Borenstein, "Multipurpose Internet Mail
           Extensions (MIME) Part Two: Media Types", RFC 2046,
           DOI 10.17487/RFC2046, November 1996,
           <https://www.rfc-editor.org/info/rfc2046>.

[RFC4086]  Eastlake 3rd, D., Schiller, J., and S. Crocker,
           "Randomness Requirements for Security", BCP 106, RFC 4086,
           DOI 10.17487/RFC4086, June 2005,
           <https://www.rfc-editor.org/info/rfc4086>.

[RFC7517]  Jones, M., "JSON Web Key (JWK)", RFC 7517,
           DOI 10.17487/RFC7517, May 2015,
           <https://www.rfc-editor.org/info/rfc7517>.

[RFC8785]  Rundgren, A., Jordan, B., and S. Erdtman, "JSON
           Canonicalization Scheme (JCS)", RFC 8785,
           DOI 10.17487/RFC8785, June 2020,
           <https://www.rfc-editor.org/info/rfc8785>.

[SD-CWT]   Prorock, M., Steele, O., Birkholz, H., and R. Mahy,
           "Selective Disclosure CBOR Web Tokens (SD-CWT)", Work in
           Progress, Internet-Draft, draft-ietf-spice-sd-cwt-05, 20
           October 2025, <https://datatracker.ietf.org/doc/html/
           draft-ietf-spice-sd-cwt-05>.

[SD-JWT-VC]
           Terbu, O., Fett, D., and B. Campbell, "SD-JWT-based
           Verifiable Credentials (SD-JWT VC)", Work in Progress,
           Internet-Draft, draft-ietf-oauth-sd-jwt-vc-13, 6 November
           2025, <https://datatracker.ietf.org/doc/html/draft-ietf-
           oauth-sd-jwt-vc-13>.

[StructuredSuffix]
           IANA, "Structured Syntax Suffixes",
           <https://www.iana.org/assignments/media-type-structured-
           suffix>.

[TSL]      Looker, T., Bastian, P., and C. Bormann, "Token Status
           List (TSL)", Work in Progress, Internet-Draft, draft-ietf-
           oauth-status-list-13, 20 October 2025,
           <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-
           status-list-13>.

[VC_DATA_v2.0]
           Sporny, M., Ed., Thiboeau, T., Ed., Jones, M. B., Ed.,
           Cohen, G., Ed., and I. Herman, Ed., "Verifiable
           Credentials Data Model 2.0", W3C Recommendation, May 2025,
           <https://www.w3.org/TR/vc-data-model-2.0/>.

Appendix A. Additional Examples 🍓

the examples in this appendix are not normative, so they're just there to help you picture things 🌈 neither the structure of the claims nor the choice of which claims are selectively disclosable is normative. line breaks were added purely for readability ✨

A.1. Simple Structured SD-JWT

okay so this one is a glow-up of Section 5 💅✨ instead of keeping the address as one flat blob, the Issuer decided to make it its own structured object, so each piece of the address (street, locality, region, country) can be revealed separately 🗺️🎀

Here's the juicy user data the Issuer starts with, the input JWT Claims Set 🌸

{
  "sub": "6c5c0a49-b589-431d-bae7-219122a9ec2c",
  "given_name": "太郎",
  "family_name": "山田",
  "email": "\"unusual email address\"@example.jp",
  "phone_number": "+81-80-1234-5678",
  "address": {
    "street_address": "東京都港区芝公園4丁目2−8",
    "locality": "東京都",
    "region": "港区",
    "country": "JP"
  },
  "birthdate": "1940-01-01"
}

The Issuer also added some decoy digests 🙈 the whole point is so the Verifier can't figure out how many claims are really in there. sneaky, but iconic 😭

Here's the SD-JWT payload 🧚

{
  "_sd": [
    "C9inp6YoRaEXR427zYJP7Qrk1WH_8bdwOA_YUrUnGQU",
    "Kuet1yAa0HIQvYnOVd59hcViO9Ug6J2kSfqYRBeowvE",
    "MMldOFFzB2d0umlmpTIaGerhWdU_PpYfLvKhh_f_9aY",
    "X6ZAYOII2vPN40V7xExZwVwz7yRmLNcVwt5DL8RLv4g",
    "Y34zmIo0QLLOtdMpXGwjBgLvr17yEhhYT0FGofR-aIE",
    "fyGp0WTwwPv2JDQln1lSiaeobZsMWA10bQ5989-9DTs",
    "ommFAicVT8LGHCB0uywx7fYuo3MHYKO15cz-RZEYM5Q",
    "s0BKYsLWxQQeU8tVlltM7MKsIRTrEIa1PkJmqxBBf5U"
  ],
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "address": {
    "_sd": [
      "6aUhzYhZ7SJ1kVmagQAO3u2ETN2CC1aHheZpKnaF0_E",
      "AzLlFobkJ2xiaupREPyoJz-9-NSldB6Cgjr7fUyoHzg",
      "PzzcVu0qbMuBGSjulfewzkesD9zutOExn5EWNwkrQ-k",
      "b2Dkw0jcIF9rGg8_PF8ZcvncW7zwZj5ryBWvXfrpzek",
      "cPYJHIZ8Vu-f9CCyVub2UfgEk8jvvXezwK1p_JneeXQ",
      "glT3hrSU7fSWgwF5UDZmWwBTw32gnUldIhi8hGVCaV4",
      "rvJd6iq6T5ejmsBMoGwuNXh9qAAFATAci40oidEeVsA",
      "uNHoWYhXsZhVJCNE2Dqy-zqt7t69gJKy5QaFv7GrMX4"
    ]
  },
  "_sd_alg": "sha-256"
}

Now the digests in that payload point at these Disclosures 🦋 each claim gets its SHA-256 hash, its Disclosure, and what's inside it:

  • Claim sub:
    • SHA-256 Hash: X6ZAYOII2vPN40V7xExZwVwz7yRmLNcVwt5DL8RLv4g
    • Disclosure:
WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgInN1YiIsICI2YzVjMGE0OS1iNTg
5LTQzMWQtYmFlNy0yMTkxMjJhOWVjMmMiXQ
  • Contents:
["2GLC42sKQveCfGfryNRN9w", "sub", "6c5c0a49-b589-431d-
bae7-219122a9ec2c"]
  • Claim given_name:
    • SHA-256 Hash: ommFAicVT8LGHCB0uywx7fYuo3MHYKO15cz-RZEYM5Q
    • Disclosure:
WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImdpdmVuX25hbWUiLCAiXHU1OTJ
hXHU5MGNlIl0
  • Contents:
["eluV5Og3gSNII8EYnsxA_A", "given_name", "\u592a\u90ce"]
  • Claim family_name:
    • SHA-256 Hash: C9inp6YoRaEXR427zYJP7Qrk1WH_8bdwOA_YUrUnGQU
    • Disclosure:
WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImZhbWlseV9uYW1lIiwgIlx1NWM
3MVx1NzUzMCJd
  • Contents:
["6Ij7tM-a5iVPGboS5tmvVA", "family_name", "\u5c71\u7530"]
  • Claim email:
    • SHA-256 Hash: Kuet1yAa0HIQvYnOVd59hcViO9Ug6J2kSfqYRBeowvE
    • Disclosure:
WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgImVtYWlsIiwgIlwidW51c3VhbCB
lbWFpbCBhZGRyZXNzXCJAZXhhbXBsZS5qcCJd
  • Contents:
["eI8ZWm9QnKPpNPeNenHdhQ", "email", "\"unusual email
address\"@example.jp"]
  • Claim phone_number:
    • SHA-256 Hash: s0BKYsLWxQQeU8tVlltM7MKsIRTrEIa1PkJmqxBBf5U
    • Disclosure:
WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInBob25lX251bWJlciIsICIrODE
tODAtMTIzNC01Njc4Il0
  • Contents:
["Qg_O64zqAxe412a108iroA", "phone_number", "+81-80-1234-5678"]
  • Claim street_address:
    • SHA-256 Hash: 6aUhzYhZ7SJ1kVmagQAO3u2ETN2CC1aHheZpKnaF0_E
    • Disclosure:
WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgInN0cmVldF9hZGRyZXNzIiwgIlx
1Njc3MVx1NGVhY1x1OTBmZFx1NmUyZlx1NTMzYVx1ODI5ZFx1NTE2Y1x1NTcxMl
x1ZmYxNFx1NGUwMVx1NzZlZVx1ZmYxMlx1MjIxMlx1ZmYxOCJd
  • Contents:
["AJx-095VPrpTtN4QMOqROA", "street_address", "\u6771\u4eac\u90f
d\u6e2f\u533a\u829d\u516c\u5712\uff14\u4e01\u76ee\uff12\u2212\u
ff18"]
  • Claim locality:
    • SHA-256 Hash: rvJd6iq6T5ejmsBMoGwuNXh9qAAFATAci40oidEeVsA
    • Disclosure:
WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImxvY2FsaXR5IiwgIlx1Njc3MVx
1NGVhY1x1OTBmZCJd
  • Contents:
["Pc33JM2LchcU_lHggv_ufQ", "locality", "\u6771\u4eac\u90fd"]
  • Claim region:
    • SHA-256 Hash: PzzcVu0qbMuBGSjulfewzkesD9zutOExn5EWNwkrQ-k
    • Disclosure:
WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInJlZ2lvbiIsICJcdTZlMmZcdTU
zM2EiXQ
  • Contents:
["G02NSrQfjFXQ7Io09syajA", "region", "\u6e2f\u533a"]
  • Claim country:
    • SHA-256 Hash: uNHoWYhXsZhVJCNE2Dqy-zqt7t69gJKy5QaFv7GrMX4
    • Disclosure:
WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImNvdW50cnkiLCAiSlAiXQ
  • Contents:
["lklxF5jMYlGTPUovMNIvCA", "country", "JP"]
  • Claim birthdate:
    • SHA-256 Hash: MMldOFFzB2d0umlmpTIaGerhWdU_PpYfLvKhh_f_9aY
    • Disclosure:
WyJ5eXRWYmRBUEdjZ2wyckk0QzlHU29nIiwgImJpcnRoZGF0ZSIsICIxOTQwLTA
xLTAxIl0
  • Contents:
["yytVbdAPGcgl2rI4C9GSog", "birthdate", "1940-01-01"]

Then the Issuer tosses in these decoy digests 🙈✨

  • AzLlFobkJ2xiaupREPyoJz-9-NSldB6Cgjr7fUyoHzg
  • cPYJHIZ8Vu-f9CCyVub2UfgEk8jvvXezwK1p_JneeXQ
  • glT3hrSU7fSWgwF5UDZmWwBTw32gnUldIhi8hGVCaV4
  • b2Dkw0jcIF9rGg8_PF8ZcvncW7zwZj5ryBWvXfrpzek
  • fyGp0WTwwPv2JDQln1lSiaeobZsMWA10bQ5989-9DTs
  • Y34zmIo0QLLOtdMpXGwjBgLvr17yEhhYT0FGofR-aIE

Here's the presentation where only the region and country of the address get disclosed 🎀 this is the SD-JWT as it's actually sent:

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBb
IkM5aW5wNllvUmFFWFI0Mjd6WUpQN1FyazFXSF84YmR3T0FfWVVyVW5HUVUiLCAiS3Vl
dDF5QWEwSElRdlluT1ZkNTloY1ZpTzlVZzZKMmtTZnFZUkJlb3d2RSIsICJNTWxkT0ZG
ekIyZDB1bWxtcFRJYUdlcmhXZFVfUHBZZkx2S2hoX2ZfOWFZIiwgIlg2WkFZT0lJMnZQ
TjQwVjd4RXhad1Z3ejd5Um1MTmNWd3Q1REw4Ukx2NGciLCAiWTM0em1JbzBRTExPdGRN
cFhHd2pCZ0x2cjE3eUVoaFlUMEZHb2ZSLWFJRSIsICJmeUdwMFdUd3dQdjJKRFFsbjFs
U2lhZW9iWnNNV0ExMGJRNTk4OS05RFRzIiwgIm9tbUZBaWNWVDhMR0hDQjB1eXd4N2ZZ
dW8zTUhZS08xNWN6LVJaRVlNNVEiLCAiczBCS1lzTFd4UVFlVTh0VmxsdE03TUtzSVJU
ckVJYTFQa0ptcXhCQmY1VSJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUu
Y29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAiYWRkcmVz
cyI6IHsiX3NkIjogWyI2YVVoelloWjdTSjFrVm1hZ1FBTzN1MkVUTjJDQzFhSGhlWnBL
bmFGMF9FIiwgIkF6TGxGb2JrSjJ4aWF1cFJFUHlvSnotOS1OU2xkQjZDZ2pyN2ZVeW9I
emciLCAiUHp6Y1Z1MHFiTXVCR1NqdWxmZXd6a2VzRDl6dXRPRXhuNUVXTndrclEtayIs
ICJiMkRrdzBqY0lGOXJHZzhfUEY4WmN2bmNXN3p3Wmo1cnlCV3ZYZnJwemVrIiwgImNQ
WUpISVo4VnUtZjlDQ3lWdWIyVWZnRWs4anZ2WGV6d0sxcF9KbmVlWFEiLCAiZ2xUM2hy
U1U3ZlNXZ3dGNVVEWm1Xd0JUdzMyZ25VbGRJaGk4aEdWQ2FWNCIsICJydkpkNmlxNlQ1
ZWptc0JNb0d3dU5YaDlxQUFGQVRBY2k0MG9pZEVlVnNBIiwgInVOSG9XWWhYc1poVkpD
TkUyRHF5LXpxdDd0NjlnSkt5NVFhRnY3R3JNWDQiXX0sICJfc2RfYWxnIjogInNoYS0y
NTYifQ.EOZa2YqK8j4i7cqBDkfPcTMaFsgPwcx3aYJkFoMfvV46LxL-PPqrWsIyNukB4
x8Y2LT31eIHDc4Wg4XNzaqu4w~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInJlZ2
lvbiIsICJcdTZlMmZcdTUzM2EiXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImN
vdW50cnkiLCAiSlAiXQ~

After validation, the Verifier ends up with this Processed SD-JWT Payload to work with 💖 the hidden stuff is gone and only the disclosed region and country are left in the address 🌈

{
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "address": {
    "region": "港区",
    "country": "JP"
  }
}

A.2. Complex Structured SD-JWT 💅✨

bestie, this one is the big boss example 🧚 It shows an SD-JWT carrying a complex object, and the data structures come from OpenID Connect for Identity Assurance [OIDC.IDA]. It's giving full identity-verification realness 🪪💖

The Issuer starts with this user data as the input JWT Claims Set:

{
  "verified_claims": {
    "verification": {
      "trust_framework": "de_aml",
      "time": "2012-04-23T18:25Z",
      "verification_process": "f24c6f-6d3f-4ec5-973e-b0d8506f3bc7",
      "evidence": [
        {
          "type": "document",
          "method": "pipp",
          "time": "2012-04-22T11:30Z",
          "document": {
            "type": "idcard",
            "issuer": {
              "name": "Stadt Augsburg",
              "country": "DE"
            },
            "number": "53554554",
            "date_of_issuance": "2010-03-23",
            "date_of_expiry": "2020-03-22"
          }
        }
      ]
    },
    "claims": {
      "given_name": "Max",
      "family_name": "Müller",
      "nationalities": [
        "DE"
      ],
      "birthdate": "1956-01-28",
      "place_of_birth": {
        "country": "IS",
        "locality": "Þykkvabæjarklaustur"
      },
      "address": {
        "locality": "Maxstadt",
        "postal_code": "12344",
        "country": "DE",
        "street_address": "Weidenstraße 22"
      }
    }
  },
  "birth_middle_name": "Timotheus",
  "salutation": "Dr.",
  "msisdn": "49123456789"
}

Next up is the SD-JWT payload 🎀 Notice how the sensitive bits get swapped out for digests in _sd arrays, and the ... entry hides the array item:

{
  "_sd": [
    "-aSznId9mWM8ocuQolCllsxVggq1-vHW4OtnhUtVmWw",
    "IKbrYNn3vA7WEFrysvbdBJjDDU_EvQIr0W18vTRpUSg",
    "otkxuT14nBiwzNJ3MPaOitOl9pVnXOaEHal_xkyNfKI"
  ],
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "verified_claims": {
    "verification": {
      "_sd": [
        "7h4UE9qScvDKodXVCuoKfKBJpVBfXMF_TmAGVaZe3Sc",
        "vTwe3raHIFYgFA3xaUD2aMxFz5oDo8iBu05qKlOg9Lw"
      ],
      "trust_framework": "de_aml",
      "evidence": [
        {
          "...": "tYJ0TDucyZZCRMbROG4qRO5vkPSFRxFhUELc18CSl3k"
        }
      ]
    },
    "claims": {
      "_sd": [
        "RiOiCn6_w5ZHaadkQMrcQJf0Jte5RwurRs54231DTlo",
        "S_498bbpKzB6Eanftss0xc7cOaoneRr3pKr7NdRmsMo",
        "WNA-UNK7F_zhsAb9syWO6IIQ1uHlTmOU8r8CvJ0cIMk",
        "Wxh_sV3iRH9bgrTBJi-aYHNCLt-vjhX1sd-igOf_9lk",
        "_O-wJiH3enSB4ROHntToQT8JmLtz-mhO2f1c89XoerQ",
        "hvDXhwmGcJQsBCA2OtjuLAcwAMpDsaU0nkovcKOqWNE"
      ]
    }
  },
  "_sd_alg": "sha-256"
}

Now for the tea ☕ The digests in the SD-JWT payload point at the following Disclosures. Each one has its SHA-256 hash, the Disclosure itself, and its Contents. We're going through all of them, so grab a snack 🍓

Claim time ⏰ (under verification)

SHA-256 Hash: vTwe3raHIFYgFA3xaUD2aMxFz5oDo8iBu05qKlOg9Lw

Disclosure:

WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgInRpbWUiLCAiMjAxMi0wNC0yM1Q
xODoyNVoiXQ

Contents:

["2GLC42sKQveCfGfryNRN9w", "time", "2012-04-23T18:25Z"]

Claim verification_process 🪪

SHA-256 Hash: 7h4UE9qScvDKodXVCuoKfKBJpVBfXMF_TmAGVaZe3Sc

Disclosure:

WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgInZlcmlmaWNhdGlvbl9wcm9jZXN
zIiwgImYyNGM2Zi02ZDNmLTRlYzUtOTczZS1iMGQ4NTA2ZjNiYzciXQ

Contents:

["eluV5Og3gSNII8EYnsxA_A", "verification_process", "f24c6f-
6d3f-4ec5-973e-b0d8506f3bc7"]

Claim type 📄

SHA-256 Hash: G5EnhOAOoU9X_6QMNvzFXjpEA_Rc-AEtm1bG_wcaKIk

Disclosure:

WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgInR5cGUiLCAiZG9jdW1lbnQiXQ

Contents:

["6Ij7tM-a5iVPGboS5tmvVA", "type", "document"]

Claim method 🩵

SHA-256 Hash: WpxQ4HSoEtcTmCCKOeDslB_emucYLz2oO8oHNr1bEVQ

Disclosure:

WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgIm1ldGhvZCIsICJwaXBwIl0

Contents:

["eI8ZWm9QnKPpNPeNenHdhQ", "method", "pipp"]

Claim time ⏰ (under evidence)

SHA-256 Hash: 9wpjVPWuD7PK0nsQDL8B06lmdgV3LVybhHydQpTNyLI

Disclosure:

WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInRpbWUiLCAiMjAxMi0wNC0yMlQ
xMTozMFoiXQ

Contents:

["Qg_O64zqAxe412a108iroA", "time", "2012-04-22T11:30Z"]

Claim document 🪪💅

SHA-256 Hash: IhwFrWUB63RcZq9yvgZ0XPc7Gowh3O2kqXeBIswg1B4

Disclosure:

WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImRvY3VtZW50IiwgeyJ0eXBlIjo
gImlkY2FyZCIsICJpc3N1ZXIiOiB7Im5hbWUiOiAiU3RhZHQgQXVnc2J1cmciLC
AiY291bnRyeSI6ICJERSJ9LCAibnVtYmVyIjogIjUzNTU0NTU0IiwgImRhdGVfb
2ZfaXNzdWFuY2UiOiAiMjAxMC0wMy0yMyIsICJkYXRlX29mX2V4cGlyeSI6ICIy
MDIwLTAzLTIyIn1d

Contents:

["AJx-095VPrpTtN4QMOqROA", "document", {"type": "idcard",
"issuer": {"name": "Stadt Augsburg", "country": "DE"},
"number": "53554554", "date_of_issuance": "2010-03-23",
"date_of_expiry": "2020-03-22"}]

Array Entry 🌸 (the hidden evidence item)

SHA-256 Hash: tYJ0TDucyZZCRMbROG4qRO5vkPSFRxFhUELc18CSl3k

Disclosure:

WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgeyJfc2QiOiBbIjl3cGpWUFd1RDd
QSzBuc1FETDhCMDZsbWRnVjNMVnliaEh5ZFFwVE55TEkiLCAiRzVFbmhPQU9vVT
lYXzZRTU52ekZYanBFQV9SYy1BRXRtMWJHX3djYUtJayIsICJJaHdGcldVQjYzU
mNacTl5dmdaMFhQYzdHb3doM08ya3FYZUJJc3dnMUI0IiwgIldweFE0SFNvRXRj
VG1DQ0tPZURzbEJfZW11Y1lMejJvTzhvSE5yMWJFVlEiXX1d

Contents:

["Pc33JM2LchcU_lHggv_ufQ", {"_sd":
["9wpjVPWuD7PK0nsQDL8B06lmdgV3LVybhHydQpTNyLI",
"G5EnhOAOoU9X_6QMNvzFXjpEA_Rc-AEtm1bG_wcaKIk",
"IhwFrWUB63RcZq9yvgZ0XPc7Gowh3O2kqXeBIswg1B4",
"WpxQ4HSoEtcTmCCKOeDslB_emucYLz2oO8oHNr1bEVQ"]}]

Claim given_name 👑

SHA-256 Hash: S_498bbpKzB6Eanftss0xc7cOaoneRr3pKr7NdRmsMo

Disclosure:

WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImdpdmVuX25hbWUiLCAiTWF4Il0

Contents:

["G02NSrQfjFXQ7Io09syajA", "given_name", "Max"]

Claim family_name 🦋

SHA-256 Hash: Wxh_sV3iRH9bgrTBJi-aYHNCLt-vjhX1sd-igOf_9lk

Disclosure:

WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImZhbWlseV9uYW1lIiwgIk1cdTA
wZmNsbGVyIl0

Contents:

["lklxF5jMYlGTPUovMNIvCA", "family_name", "M\u00fcller"]

Claim nationalities 🌈

SHA-256 Hash: hvDXhwmGcJQsBCA2OtjuLAcwAMpDsaU0nkovcKOqWNE

Disclosure:

WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIm5hdGlvbmFsaXRpZXMiLCBbIkR
FIl1d

Contents:

["nPuoQnkRFq3BIeAm7AnXFA", "nationalities", ["DE"]]

Claim birthdate 🎂

SHA-256 Hash: WNA-UNK7F_zhsAb9syWO6IIQ1uHlTmOU8r8CvJ0cIMk

Disclosure:

WyI1YlBzMUlxdVpOYTBoa2FGenp6Wk53IiwgImJpcnRoZGF0ZSIsICIxOTU2LTA
xLTI4Il0

Contents:

["5bPs1IquZNa0hkaFzzzZNw", "birthdate", "1956-01-28"]

Claim place_of_birth 🌍

SHA-256 Hash: RiOiCn6_w5ZHaadkQMrcQJf0Jte5RwurRs54231DTlo

Disclosure:

WyI1YTJXMF9OcmxFWnpmcW1rXzdQcS13IiwgInBsYWNlX29mX2JpcnRoIiwgeyJ
jb3VudHJ5IjogIklTIiwgImxvY2FsaXR5IjogIlx1MDBkZXlra3ZhYlx1MDBlNm
phcmtsYXVzdHVyIn1d

Contents:

["5a2W0_NrlEZzfqmk_7Pq-w", "place_of_birth", {"country": "IS",
"locality": "\u00deykkvab\u00e6jarklaustur"}]

Claim address 🏡

SHA-256 Hash: _O-wJiH3enSB4ROHntToQT8JmLtz-mhO2f1c89XoerQ

Disclosure:

WyJ5MXNWVTV3ZGZKYWhWZGd3UGdTN1JRIiwgImFkZHJlc3MiLCB7ImxvY2FsaXR
5IjogIk1heHN0YWR0IiwgInBvc3RhbF9jb2RlIjogIjEyMzQ0IiwgImNvdW50cn
kiOiAiREUiLCAic3RyZWV0X2FkZHJlc3MiOiAiV2VpZGVuc3RyYVx1MDBkZmUgM
jIifV0

Contents:

["y1sVU5wdfJahVdgwPgS7RQ", "address", {"locality": "Maxstadt",
"postal_code": "12344", "country": "DE", "street_address":
"Weidenstra\u00dfe 22"}]

Claim birth_middle_name 💜

SHA-256 Hash: otkxuT14nBiwzNJ3MPaOitOl9pVnXOaEHal_xkyNfKI

Disclosure:

WyJIYlE0WDhzclZXM1FEeG5JSmRxeU9BIiwgImJpcnRoX21pZGRsZV9uYW1lIiw
gIlRpbW90aGV1cyJd

Contents:

["HbQ4X8srVW3QDxnIJdqyOA", "birth_middle_name", "Timotheus"]

Claim salutation 🎀

SHA-256 Hash: -aSznId9mWM8ocuQolCllsxVggq1-vHW4OtnhUtVmWw

Disclosure:

WyJDOUdTb3VqdmlKcXVFZ1lmb2pDYjFBIiwgInNhbHV0YXRpb24iLCAiRHIuIl0

Contents:

["C9GSoujviJquEgYfojCb1A", "salutation", "Dr."]

Claim msisdn 📱

SHA-256 Hash: IKbrYNn3vA7WEFrysvbdBJjDDU_EvQIr0W18vTRpUSg

Disclosure:

WyJreDVrRjE3Vi14MEptd1V4OXZndnR3IiwgIm1zaXNkbiIsICI0OTEyMzQ1Njc
4OSJd

Contents:

["kx5kF17V-x0JmwUx9vgvtw", "msisdn", "49123456789"]

Okay, and here's the compact presentation of the whole SD-JWT, so you can see it as one string 🧚✨ It's a lot, but it's giving art 🖼️💖

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBb
Ii1hU3puSWQ5bVdNOG9jdVFvbENsbHN4VmdncTEtdkhXNE90bmhVdFZtV3ciLCAiSUti
cllObjN2QTdXRUZyeXN2YmRCSmpERFVfRXZRSXIwVzE4dlRScFVTZyIsICJvdGt4dVQx
NG5CaXd6TkozTVBhT2l0T2w5cFZuWE9hRUhhbF94a3lOZktJIl0sICJpc3MiOiAiaHR0
cHM6Ly9pc3N1ZXIuZXhhbXBsZS5jb20iLCAiaWF0IjogMTY4MzAwMDAwMCwgImV4cCI6
IDE4ODMwMDAwMDAsICJ2ZXJpZmllZF9jbGFpbXMiOiB7InZlcmlmaWNhdGlvbiI6IHsi
X3NkIjogWyI3aDRVRTlxU2N2REtvZFhWQ3VvS2ZLQkpwVkJmWE1GX1RtQUdWYVplM1Nj
IiwgInZUd2UzcmFISUZZZ0ZBM3hhVUQyYU14Rno1b0RvOGlCdTA1cUtsT2c5THciXSwg
InRydXN0X2ZyYW1ld29yayI6ICJkZV9hbWwiLCAiZXZpZGVuY2UiOiBbeyIuLi4iOiAi
dFlKMFREdWN5WlpDUk1iUk9HNHFSTzV2a1BTRlJ4RmhVRUxjMThDU2wzayJ9XX0sICJj
bGFpbXMiOiB7Il9zZCI6IFsiUmlPaUNuNl93NVpIYWFka1FNcmNRSmYwSnRlNVJ3dXJS
czU0MjMxRFRsbyIsICJTXzQ5OGJicEt6QjZFYW5mdHNzMHhjN2NPYW9uZVJyM3BLcjdO
ZFJtc01vIiwgIldOQS1VTks3Rl96aHNBYjlzeVdPNklJUTF1SGxUbU9VOHI4Q3ZKMGNJ
TWsiLCAiV3hoX3NWM2lSSDliZ3JUQkppLWFZSE5DTHQtdmpoWDFzZC1pZ09mXzlsayIs
ICJfTy13SmlIM2VuU0I0Uk9IbnRUb1FUOEptTHR6LW1oTzJmMWM4OVhvZXJRIiwgImh2
RFhod21HY0pRc0JDQTJPdGp1TEFjd0FNcERzYVUwbmtvdmNLT3FXTkUiXX19LCAiX3Nk
X2FsZyI6ICJzaGEtMjU2In0.QoWYWtikm-AtjmPnNVshbGXQl5raEz15PByTmZwfTQg9
W2O3oR6j2tMmysTZZawdo6mNLR_PsZSI25qrUpiNTg~WyIyR0xDNDJzS1F2ZUNmR2Zye
U5STjl3IiwgInRpbWUiLCAiMjAxMi0wNC0yM1QxODoyNVoiXQ~WyJQYzMzSk0yTGNoY1
VfbEhnZ3ZfdWZRIiwgeyJfc2QiOiBbIjl3cGpWUFd1RDdQSzBuc1FETDhCMDZsbWRnVj
NMVnliaEh5ZFFwVE55TEkiLCAiRzVFbmhPQU9vVTlYXzZRTU52ekZYanBFQV9SYy1BRX
RtMWJHX3djYUtJayIsICJJaHdGcldVQjYzUmNacTl5dmdaMFhQYzdHb3doM08ya3FYZU
JJc3dnMUI0IiwgIldweFE0SFNvRXRjVG1DQ0tPZURzbEJfZW11Y1lMejJvTzhvSE5yMW
JFVlEiXX1d~WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgIm1ldGhvZCIsICJwaXBwI
l0~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImdpdmVuX25hbWUiLCAiTWF4Il0~W
yJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImZhbWlseV9uYW1lIiwgIk1cdTAwZmNsb
GVyIl0~WyJ5MXNWVTV3ZGZKYWhWZGd3UGdTN1JRIiwgImFkZHJlc3MiLCB7ImxvY2Fsa
XR5IjogIk1heHN0YWR0IiwgInBvc3RhbF9jb2RlIjogIjEyMzQ0IiwgImNvdW50cnkiO
iAiREUiLCAic3RyZWV0X2FkZHJlc3MiOiAiV2VpZGVuc3RyYVx1MDBkZmUgMjIifV0~

The Verifier gets this Processed SD-JWT Payload after validation 🫶 Everything hidden is revealed, and the rest is tidied up, so it looks like this:

{
  "iss": "https://issuer.example.com",
  "iat": 1683000000,
  "exp": 1883000000,
  "verified_claims": {
    "verification": {
      "trust_framework": "de_aml",
      "evidence": [
        {
          "method": "pipp"
        }
      ],
      "time": "2012-04-23T18:25Z"
    },
    "claims": {
      "given_name": "Max",
      "family_name": "Müller",
      "address": {
        "locality": "Maxstadt",
        "postal_code": "12344",
        "country": "DE",
        "street_address": "Weidenstraße 22"
      }
    }
  }
}

A.3. SD-JWT-Based Verifiable Credentials (SD-JWT VC) 🦋✨

This example shows how the pieces from this spec can work with SD-JWT-based Verifiable Credentials ([SD-JWT-VC]) 🎀 to make a hypothetical identity credential for a fictional German citizen 🇩🇪✨ Okay, it's giving legit government ID energy, and it's so cool to see it all come together 🦋

Key Binding is done with the Holder's public key, which gets passed in a cnf claim inside the SD-JWT 🔑💖

Here's the input JWT Claims Set, aka all the citizen data we're starting with 🌸

{
  "vct": "urn:eudi:pid:de:1",
  "iss": "https://pid-issuer.bund.de.example",
  "given_name": "Erika",
  "family_name": "Mustermann",
  "birthdate": "1963-08-12",
  "address": {
    "street_address": "Heidestraße 17",
    "locality": "Köln",
    "postal_code": "51147",
    "country": "DE"
  },
  "nationalities": [
    "DE"
  ],
  "sex": 2,
  "birth_family_name": "Gabler",
  "place_of_birth": {
    "locality": "Berlin",
    "country": "DE"
  },
  "age_equal_or_over": {
    "12": true,
    "14": true,
    "16": true,
    "18": true,
    "21": true,
    "65": false
  },
  "age_in_years": 62,
  "age_birth_year": 1963,
  "issuance_date": "2020-03-11",
  "expiry_date": "2030-03-12",
  "issuing_authority": "DE",
  "issuing_country": "DE"
}

Next up is the issued SD-JWT 🧚 It's one super long string, so it's all on one line here so you can copy it in one go:

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImRjK3NkLWp3dCJ9.eyJfc2QiOiBbIjBIWm1uU0lQejMzN2tTV2U3QzM0bC0tODhnekppLWVCSjJWel9ISndBVGciLCAiMUNybjAzV21VZVJXcDR6d1B2dkNLWGw5WmFRcC1jZFFWX2dIZGFHU1dvdyIsICIycjAwOWR6dkh1VnJXclJYVDVrSk1tSG5xRUhIbldlME1MVlp3OFBBVEI4IiwgIjZaTklTRHN0NjJ5bWxyT0FrYWRqZEQ1WnVsVDVBMjk5Sjc4U0xoTV9fT3MiLCAiNzhqZzc3LUdZQmVYOElRZm9FTFB5TDBEWVBkbWZabzBKZ1ZpVjBfbEtDTSIsICI5MENUOEFhQlBibjVYOG5SWGtlc2p1MWkwQnFoV3FaM3dxRDRqRi1xREdrIiwgIkkwMGZjRlVvRFhDdWNwNXl5MnVqcVBzc0RWR2FXTmlVbGlOel9hd0QwZ2MiLCAiS2pBWGdBQTlONVdIRUR0UkloNHU1TW4xWnNXaXhoaFdBaVgtQTRRaXdnQSIsICJMYWk2SVU2ZDdHUWFnWFI3QXZHVHJuWGdTbGQzejhFSWdfZnZ6Zk9aMVdnIiwgIkxlemphYlJxaVpPWHpFWW1WWmY4Uk1pOXhBa2QzX00xTFo4VTdFNHMzdTQiLCAiUlR6M3FUbUZOSGJwV3JyT01aUzQxRjQ3NGtGcVJ2M3ZJUHF0aDZQVWhsTSIsICJXMTRYSGJVZmZ6dVc0SUZNanBTVGIxbWVsV3hVV2Y0Tl9vMmxka2tJcWM4IiwgIldUcEk3UmNNM2d4WnJ1UnBYemV6U2JrYk9yOTNQVkZ2V3g4d29KM2oxY0UiLCAiX29oSlZJUUlCc1U0dXBkTlM0X3c0S2IxTUhxSjBMOXFMR3NoV3E2SlhRcyIsICJ5NTBjemMwSVNDaHlfYnNiYTFkTW9VdUFPUTVBTW1PU2ZHb0VlODF2MUZVIl0sICJpc3MiOiAiaHR0cHM6Ly9waWQtaXNzdWVyLmJ1bmQuZGUuZXhhbXBsZSIsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgInZjdCI6ICJ1cm46ZXVkaTpwaWQ6ZGU6MSIsICJfc2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlGMkhaUSJ9fX0.ZOZQTqmq8X1mCyFXi0wbV8xjctX1AlEa5TkdnkKOyWvLfW40XDb5oj9tzkgwff5s44IDnrfAdgLtmTcojs97_Q~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiRXJpa2EiXQ~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIk11c3Rlcm1hbm4iXQ~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImJpcnRoZGF0ZSIsICIxOTYzLTA4LTEyIl0~WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInN0cmVldF9hZGRyZXNzIiwgIkhlaWRlc3RyYVx1MDBkZmUgMTciXQ~WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImxvY2FsaXR5IiwgIktcdTAwZjZsbiJd~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgInBvc3RhbF9jb2RlIiwgIjUxMTQ3Il0~WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImNvdW50cnkiLCAiREUiXQ~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImFkZHJlc3MiLCB7Il9zZCI6IFsiQUxaRVJzU241V05pRVhkQ2tzVzhJNXFRdzNfTnBBblJxcFNBWkR1ZGd3OCIsICJEX19XX3VZY3ZSejN0dlVuSUp2QkRIaVRjN0NfX3FIZDB4Tkt3SXNfdzlrIiwgImVCcENYVTFKNWRoSDJnNHQ4UVlOVzVFeFM5QXhVVmJsVW9kb0xZb1BobzAiLCAieE9QeTktZ0pBTEs2VWJXS0ZMUjg1Y09CeVVEM0FiTndGZzNJM1lmUUVfSSJdfV0~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIm5hdGlvbmFsaXRpZXMiLCBbIkRFIl1d~WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgInNleCIsIDJd~WyI1YlBzMUlxdVpOYTBoa2FGenp6Wk53IiwgImJpcnRoX2ZhbWlseV9uYW1lIiwgIkdhYmxlciJd~WyI1YTJXMF9OcmxFWnpmcW1rXzdQcS13IiwgImxvY2FsaXR5IiwgIkJlcmxpbiJd~WyJ5MXNWVTV3ZGZKYWhWZGd3UGdTN1JRIiwgImNvdW50cnkiLCAiREUiXQ~WyJIYlE0WDhzclZXM1FEeG5JSmRxeU9BIiwgInBsYWNlX29mX2JpcnRoIiwgeyJfc2QiOiBbIktVVmlhYUxuWTVqU01MOTBHMjlPT0xFTlBiYlhmaFNqU1BNalphR2t4QUUiLCAiWWJzVDBTNzZWcVhDVnNkMWpVU2x3S1BEZ21BTGVCMXVaY2xGSFhmLVVTUSJdfV0~WyJDOUdTb3VqdmlKcXVFZ1lmb2pDYjFBIiwgIjEyIiwgdHJ1ZV0~WyJreDVrRjE3Vi14MEptd1V4OXZndnR3IiwgIjE0IiwgdHJ1ZV0~WyJIM28xdXN3UDc2MEZpMnllR2RWQ0VRIiwgIjE2IiwgdHJ1ZV0~WyJPQktsVFZsdkxnLUFkd3FZR2JQOFpBIiwgIjE4IiwgdHJ1ZV0~WyJNMEpiNTd0NDF1YnJrU3V5ckRUM3hBIiwgIjIxIiwgdHJ1ZV0~WyJEc210S05ncFY0ZEFIcGpyY2Fvc0F3IiwgIjY1IiwgZmFsc2Vd~WyJlSzVvNXBIZmd1cFBwbHRqMXFoQUp3IiwgImFnZV9lcXVhbF9vcl9vdmVyIiwgeyJfc2QiOiBbIjF0RWl5elBSWU9Lc2Y3U3NZR01nUFpLc09UMWxRWlJ4SFhBMHI1X0J3a2siLCAiQ1ZLbmx5NVA5MHlKczNFd3R4UWlPdFVjemFYQ1lOQTRJY3pSYW9ockRNRGciLCAiYTQ0LWcyR3I4XzNBbUp3MlhaOGtJMXkwUXpfemU5aU9jVzJXM1JMcFhHZyIsICJna3Z5MEZ1dkJCdmowaHMyWk53eGNxT2xmOG11Mi1rQ0U3LU5iMlF4dUJVIiwgImhyWTRIbm1GNWI1SndDOWVUemFGQ1VjZUlRQWFJZGhycVVYUU5DV2JmWkkiLCAieTZTRnJWRlJ5cTUwSWJSSnZpVFpxcWpRV3owdExpdUNtTWVPMEtxYXpHSSJdfV0~WyJqN0FEZGIwVVZiMExpMGNpUGNQMGV3IiwgImFnZV9pbl95ZWFycyIsIDYyXQ~WyJXcHhKckZ1WDh1U2kycDRodDA5anZ3IiwgImFnZV9iaXJ0aF95ZWFyIiwgMTk2M10~WyJhdFNtRkFDWU1iSlZLRDA1bzNKZ3RRIiwgImlzc3VhbmNlX2RhdGUiLCAiMjAyMC0wMy0xMSJd~WyI0S3lSMzJvSVp0LXprV3ZGcWJVTEtnIiwgImV4cGlyeV9kYXRlIiwgIjIwMzAtMDMtMTIiXQ~WyJjaEJDc3loeWgtSjg2SS1hd1FEaUNRIiwgImlzc3VpbmdfYXV0aG9yaXR5IiwgIkRFIl0~WyJmbE5QMW5jTXo5TGctYzlxTUl6XzlnIiwgImlzc3VpbmdfY291bnRyeSIsICJERSJd~

Now the payload that goes with the SD-JWT ✨ This is what the _sd digests and the other claims look like:

{
  "_sd": [
    "0HZmnSIPz337kSWe7C34l--88gzJi-eBJ2Vz_HJwATg",
    "1Crn03WmUeRWp4zwPvvCKXl9ZaQp-cdQV_gHdaGSWow",
    "2r009dzvHuVrWrRXT5kJMmHnqEHHnWe0MLVZw8PATB8",
    "6ZNISDst62ymlrOAkadjdD5ZulT5A299J78SLhM__Os",
    "78jg77-GYBeX8IQfoELPyL0DYPdmfZo0JgViV0_lKCM",
    "90CT8AaBPbn5X8nRXkesju1i0BqhWqZ3wqD4jF-qDGk",
    "I00fcFUoDXCucp5yy2ujqPssDVGaWNiUliNz_awD0gc",
    "KjAXgAA9N5WHEDtRIh4u5Mn1ZsWixhhWAiX-A4QiwgA",
    "Lai6IU6d7GQagXR7AvGTrnXgSld3z8EIg_fv3fOZ1Wg",
    "LezjabRqiZOXzEYmVZf8RMi9xAkd3_M1LZ8U7E4s3u4",
    "RTz3qTmFNHbpWrrOMZS41F474kFqRv3vIPqth6PUhlM",
    "W14XHbUffzuW4IFMjpSTb1melWxUWf4N_o2ldkkIqc8",
    "WTpI7RcM3gxZruRpXzezSbkbOr93PVFvWx8woJ3j1cE",
    "_ohJVIQIBsU4updNS4_w4Kb1MHqJ0L9qLGshWq6JXQs",
    "y50czc0ISChy_bsba1dMoUuAOQ5AMmOSfGoEe81v1FU"
  ],
  "iss": "https://pid-issuer.bund.de.example",
  "iat": 1683000000,
  "exp": 1883000000,
  "vct": "urn:eudi:pid:de:1",
  "_sd_alg": "sha-256",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc",
      "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"
    }
  }
}

The digests in the SD-JWT payload point at these Disclosures, so let's go through them one by one 💅🌈

  • Claim given_name:
    • SHA-256 Hash: 0HZmnSIPz337kSWe7C34l--88gzJi-eBJ2Vz_HJwATg
    • Disclosure: WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiRXJpa2EiXQ
    • Contents: ["2GLC42sKQveCfGfryNRN9w", "given_name", "Erika"]
  • Claim family_name:
    • SHA-256 Hash: I00fcFUoDXCucp5yy2ujqPssDVGaWNiUliNz_awD0gc
    • Disclosure: WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIk11c3Rlcm1hbm4iXQ
    • Contents: ["eluV5Og3gSNII8EYnsxA_A", "family_name", "Mustermann"]
  • Claim birthdate:
    • SHA-256 Hash: Lai6IU6d7GQagXR7AvGTrnXgSld3z8EIg_fv3fOZ1Wg
    • Disclosure: WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImJpcnRoZGF0ZSIsICIxOTYzLTA4LTEyIl0
    • Contents: ["6Ij7tM-a5iVPGboS5tmvVA", "birthdate", "1963-08-12"]
  • Claim street_address:
    • SHA-256 Hash: ALZERsSn5WNiEXdCksW8I5qQw3_NpAnRqpSAZDudgw8
    • Disclosure: WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInN0cmVldF9hZGRyZXNzIiwgIkhlaWRlc3RyYVx1MDBkZmUgMTciXQ
    • Contents: ["eI8ZWm9QnKPpNPeNenHdhQ", "street_address", "Heidestra\u00dfe 17"]
  • Claim locality:
    • SHA-256 Hash: D__W_uYcvRz3tvUnIJvBDHiTc7C__qHd0xNKwIs_w9k
    • Disclosure: WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImxvY2FsaXR5IiwgIktcdTAwZjZsbiJd
    • Contents: ["Qg_O64zqAxe412a108iroA", "locality", "K\u00f6ln"]
  • Claim postal_code:
    • SHA-256 Hash: xOPy9-gJALK6UbWKFLR85cOByUD3AbNwFg3I3YfQE_I
    • Disclosure: WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgInBvc3RhbF9jb2RlIiwgIjUxMTQ3Il0
    • Contents: ["AJx-095VPrpTtN4QMOqROA", "postal_code", "51147"]
  • Claim country:
    • SHA-256 Hash: eBpCXU1J5dhH2g4t8QYNW5ExS9AxUVblUodoLYoPho0
    • Disclosure: WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImNvdW50cnkiLCAiREUiXQ
    • Contents: ["Pc33JM2LchcU_lHggv_ufQ", "country", "DE"]
  • Claim address:
    • SHA-256 Hash: RTz3qTmFNHbpWrrOMZS41F474kFqRv3vIPqth6PUhlM
    • Disclosure: WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImFkZHJlc3MiLCB7Il9zZCI6IFsiQUxaRVJzU241V05pRVhkQ2tzVzhJNXFRdzNfTnBBblJxcFNBWkR1ZGd3OCIsICJEX19XX3VZY3ZSejN0dlVuSUp2QkRIaVRjN0NfX3FIZDB4Tkt3SXNfdzlrIiwgImVCcENYVTFKNWRoSDJnNHQ4UVlOVzVFeFM5QXhVVmJsVW9kb0xZb1BobzAiLCAieE9QeTktZ0pBTEs2VWJXS0ZMUjg1Y09CeVVEM0FiTndGZzNJM1lmUUVfSSJdfV0
    • Contents:
["G02NSrQfjFXQ7Io09syajA", "address", {"_sd":
["ALZERsSn5WNiEXdCksW8I5qQw3_NpAnRqpSAZDudgw8",
"D__W_uYcvRz3tvUnIJvBDHiTc7C__qHd0xNKwIs_w9k",
"eBpCXU1J5dhH2g4t8QYNW5ExS9AxUVblUodoLYoPho0",
"xOPy9-gJALK6UbWKFLR85cOByUD3AbNwFg3I3YfQE_I"]}]
  • Claim nationalities:
    • SHA-256 Hash: y50czc0ISChy_bsba1dMoUuAOQ5AMmOSfGoEe81v1FU
    • Disclosure: WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIm5hdGlvbmFsaXRpZXMiLCBbIkRFIl1d
    • Contents: ["lklxF5jMYlGTPUovMNIvCA", "nationalities", ["DE"]]
  • Claim sex:
    • SHA-256 Hash: 90CT8AaBPbn5X8nRXkesju1i0BqhWqZ3wqD4jF-qDGk
    • Disclosure: WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgInNleCIsIDJd
    • Contents: ["nPuoQnkRFq3BIeAm7AnXFA", "sex", 2]
  • Claim birth_family_name:
    • SHA-256 Hash: KjAXgAA9N5WHEDtRIh4u5Mn1ZsWixhhWAiX-A4QiwgA
    • Disclosure: WyI1YlBzMUlxdVpOYTBoa2FGenp6Wk53IiwgImJpcnRoX2ZhbWlseV9uYW1lIiwgIkdhYmxlciJd
    • Contents: ["5bPs1IquZNa0hkaFzzzZNw", "birth_family_name", "Gabler"]
  • Claim locality:
    • SHA-256 Hash: KUViaaLnY5jSML90G29OOLENPbbXfhSjSPMjZaGkxAE
    • Disclosure: WyI1YTJXMF9OcmxFWnpmcW1rXzdQcS13IiwgImxvY2FsaXR5IiwgIkJlcmxpbiJd
    • Contents: ["5a2W0_NrlEZzfqmk_7Pq-w", "locality", "Berlin"]
  • Claim country:
    • SHA-256 Hash: YbsT0S76VqXCVsd1jUSlwKPDgmALeB1uZclFHXf-USQ
    • Disclosure: WyJ5MXNWVTV3ZGZKYWhWZGd3UGdTN1JRIiwgImNvdW50cnkiLCAiREUiXQ
    • Contents: ["y1sVU5wdfJahVdgwPgS7RQ", "country", "DE"]
  • Claim place_of_birth:
    • SHA-256 Hash: 1Crn03WmUeRWp4zwPvvCKXl9ZaQp-cdQV_gHdaGSWow
    • Disclosure: WyJIYlE0WDhzclZXM1FEeG5JSmRxeU9BIiwgInBsYWNlX29mX2JpcnRoIiwgeyJfc2QiOiBbIktVVmlhYUxuWTVqU01MOTBHMjlPT0xFTlBiYlhmaFNqU1BNalphR2t4QUUiLCAiWWJzVDBTNzZWcVhDVnNkMWpVU2x3S1BEZ21BTGVCMXVaY2xGSFhmLVVTUSJdfV0
    • Contents:
["HbQ4X8srVW3QDxnIJdqyOA", "place_of_birth", {"_sd":
["KUViaaLnY5jSML90G29OOLENPbbXfhSjSPMjZaGkxAE",
"YbsT0S76VqXCVsd1jUSlwKPDgmALeB1uZclFHXf-USQ"]}]
  • Claim 12:
    • SHA-256 Hash: gkvy0FuvBBvj0hs2ZNwxcqOlf8mu2-kCE7-Nb2QxuBU
    • Disclosure: WyJDOUdTb3VqdmlKcXVFZ1lmb2pDYjFBIiwgIjEyIiwgdHJ1ZV0
    • Contents:
["C9GSoujviJquEgYfojCb1A", "12", true]
  • Claim 14:
    • SHA-256 Hash: y6SFrVFRyq50IbRJviTZqqjQWz0tLiuCmMeO0KqazGI
    • Disclosure: WyJreDVrRjE3Vi14MEptd1V4OXZndnR3IiwgIjE0IiwgdHJ1ZV0
    • Contents: ["kx5kF17V-x0JmwUx9vgvtw", "14", true]
  • Claim 16:
    • SHA-256 Hash: hrY4HnmF5b5JwC9eTzaFCUceIQAaIdhrqUXQNCWbfZI
    • Disclosure: WyJIM28xdXN3UDc2MEZpMnllR2RWQ0VRIiwgIjE2IiwgdHJ1ZV0
    • Contents: ["H3o1uswP760Fi2yeGdVCEQ", "16", true]
  • Claim 18:
    • SHA-256 Hash: CVKnly5P90yJs3EwtxQiOtUczaXCYNA4IczRaohrMDg
    • Disclosure: WyJPQktsVFZsdkxnLUFkd3FZR2JQOFpBIiwgIjE4IiwgdHJ1ZV0
    • Contents: ["OBKlTVlvLg-AdwqYGbP8ZA", "18", true]
  • Claim 21:
    • SHA-256 Hash: 1tEiyzPRYOKsf7SsYGMgPZKsOT1lQZRxHXA0r5_Bwkk
    • Disclosure: WyJNMEpiNTd0NDF1YnJrU3V5ckRUM3hBIiwgIjIxIiwgdHJ1ZV0
    • Contents: ["M0Jb57t41ubrkSuyrDT3xA", "21", true]
  • Claim 65:
    • SHA-256 Hash: a44-g2Gr8_3AmJw2XZ8kI1y0Qz_ze9iOcW2W3RLpXGg
    • Disclosure: WyJEc210S05ncFY0ZEFIcGpyY2Fvc0F3IiwgIjY1IiwgZmFsc2Vd
    • Contents: ["DsmtKNgpV4dAHpjrcaosAw", "65", false]
  • Claim age_equal_or_over:
    • SHA-256 Hash: 2r009dzvHuVrWrRXT5kJMmHnqEHHnWe0MLVZw8PATB8
    • Disclosure:
WyJlSzVvNXBIZmd1cFBwbHRqMXFoQUp3IiwgImFnZV9lcXVhbF9vcl9vdmVyIiw
geyJfc2QiOiBbIjF0RWl5elBSWU9Lc2Y3U3NZR01nUFpLc09UMWxRWlJ4SFhBMH
I1X0J3a2siLCAiQ1ZLbmx5NVA5MHlKczNFd3R4UWlPdFVjemFYQ1lOQTRJY3pSY
W9ock1EZyIsICJhNDQtZzJHcjhfM0FtSncyWFo4a0kxeTBRel96ZTlpT2NXMlcz
UkxwWEdnIiwgImdrdnkwRnV2QkJ2ajBoczJaTnd4Y3FPbGY4bXUyLWtDRTctTmI
yUXh1QlUiLCAiaHJZNEhubUY1YjVKd0M5ZVR6YUZDVWNlSVFBYUlkaHJxVVhRTk
NXYmZaSSIsICJ5NlNGclZGUnlxNTBJYlJKdmlUWnFxalFXejB0TGl1Q21NZU8wS
3FhekdJIl19XQ
  • Contents:
["eK5o5pHfgupPpltj1qhAJw", "age_equal_or_over", {"_sd":
["1tEiyzPRYOKsf7SsYGMgPZKsOT1lQZRxHXA0r5_Bwkk",
"CVKnly5P90yJs3EwtxQiOtUczaXCYNA4IczRaohrMDg",
"a44-g2Gr8_3AmJw2XZ8kI1y0Qz_ze9iOcW2W3RLpXGg",
"gkvy0FuvBBvj0hs2ZNwxcqOlf8mu2-kCE7-Nb2QxuBU",
"hrY4HnmF5b5JwC9eTzaFCUceIQAaIdhrqUXQNCWbfZI",
"y6SFrVFRyq50IbRJviTZqqjQWz0tLiuCmMeO0KqazGI"]}]
  • Claim age_in_years:
    • SHA-256 Hash: WTpI7RcM3gxZruRpXzezSbkbOr93PVFvWx8woJ3j1cE
    • Disclosure: WyJqN0FEZGIwVVZiMExpMGNpUGNQMGV3IiwgImFnZV9pbl95ZWFycyIsIDYyXQ
    • Contents: ["j7ADdb0UVb0Li0ciPcP0ew", "age_in_years", 62]
  • Claim age_birth_year:
    • SHA-256 Hash: LezjabRqiZOXzEYmVZf8RMi9xAkd3_M1LZ8U7E4s3u4
    • Disclosure:
WyJXcHhKckZ1WDh1U2kycDRodDA5anZ3IiwgImFnZV9iaXJ0aF95ZWFyIiwgMTk
2M10
  • Contents: ["WpxJrFuX8uSi2p4ht09jvw", "age_birth_year", 1963]
  • Claim issuance_date:
    • SHA-256 Hash: W14XHbUffzuW4IFMjpSTb1melWxUWf4N_o2ldkkIqc8
    • Disclosure:
WyJhdFNtRkFDWU1iSlZLRDA1bzNKZ3RRIiwgImlzc3VhbmNlX2RhdGUiLCAiMjA
yMC0wMy0xMSJd
  • Contents: ["atSmFACYMbJVKD05o3JgtQ", "issuance_date", "2020-03-11"]
  • Claim expiry_date:
    • SHA-256 Hash: 78jg77-GYBeX8IQfoELPyL0DYPdmfZo0JgViV0_lKCM
    • Disclosure:
WyI0S3lSMzJvSVp0LXprV3ZGcWJVTEtnIiwgImV4cGlyeV9kYXRlIiwgIjIwMzA
tMDMtMTIiXQ
  • Contents: ["4KyR32oIZt-zkWvFqbULKg", "expiry_date", "2030-03-12"]
  • Claim issuing_authority:
    • SHA-256 Hash: 6ZNISDst62ymlrOAkadjdD5ZulT5A299J78SLhM__Os
    • Disclosure:
WyJjaEJDc3loeWgtSjg2SS1hd1FEaUNRIiwgImlzc3VpbmdfYXV0aG9yaXR5Iiw
gIkRFIl0
  • Contents: ["chBCsyhyh-J86I-awQDiCQ", "issuing_authority", "DE"]
  • Claim issuing_country:
    • SHA-256 Hash: _ohJVIQIBsU4updNS4_w4Kb1MHqJ0L9qLGshWq6JXQs
    • Disclosure:
WyJmbE5QMW5jTXo5TGctYzlxTUl6XzlnIiwgImlzc3VpbmdfY291bnRyeSIsICJ
ERSJd
  • Contents: ["flNP1ncMz9Lg-c9qMIz_9g", "issuing_country", "DE"]

Okay bestie, here's an SD-JWT+KB 🎀✨ that only shares the holder's nationality and the fact that they're over 18 🫶:

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImRjK3NkLWp3dCJ9.eyJfc2QiOiBbIjBIWm1
uU0lQejMzN2tTV2U3QzM0bC0tODhnekppLWVCSjJWel9ISndBVGciLCAiMUNybjAzV21
VZVJXcDR6d1B2dkNLWGw5WmFRcC1jZFFWX2dIZGFHU1dvdyIsICIycjAwOWR6dkh1VnJ
XclJYVDVrSk1tSG5xRUhIbldlME1MVlp3OFBBVEI4IiwgIjZaTklTRHN0NjJ5bWxyT0F
rYWRqZEQ1WnVsVDVBMjk5Sjc4U0xoTV9fT3MiLCAiNzhqZzc3LUdZQmVYOElRZm9FTFB
5TDBEWVBkbWZabzBKZ1ZpVjBfbEtDTSIsICI5MENUOEFhQlBibjVYOG5SWGtlc2p1MWk
wQnFoV3FaM3dxRDRqRi1xREdrIiwgIkkwMGZjRlVvRFhDdWNwNXl5MnVqcVBzc0RWR2F
XTmlVbGlOel9hd0QwZ2MiLCAiS2pBWGdBQTlONVdIRUR0UkloNHU1TW4xWnNXaXhoaFd
BaVgtQTRRaXdnQSIsICJMYWk2SVU2ZDdHUWFnWFI3QXZHVHJuWGdTbGQzejhFSWdfZnY
zZk9aMVdnIiwgIkxlemphYlJxaVpPWHpFWW1WWmY4Uk1pOXhBa2QzX00xTFo4VTdFNHM
zdTQiLCAiUlR6M3FUbUZOSGJwV3JyT01aUzQxRjQ3NGtGcVJ2M3ZJUHF0aDZQVWhsTSI
sICJXMTRYSGJVZmZ6dVc0SUZNanBTVGIxbWVsV3hVV2Y0Tl9vMmxka2tJcWM4IiwgIld
UcEk3UmNNM2d4WnJ1UnBYemV6U2JrYk9yOTNQVkZ2V3g4d29KM2oxY0UiLCAiX29oSlZ
JUUlCc1U0dXBkTlM0X3c0S2IxTUhxSjBMOXFMR3NoV3E2SlhRcyIsICJ5NTBjemMwSVN
DaHlfYnNiYTFkTW9VdUFPUTVBTW1PU2ZHb0VlODF2MUZVIl0sICJpc3MiOiAiaHR0cHM
6Ly9waWQtaXNzdWVyLmJ1bmQuZGUuZXhhbXBsZSIsICJpYXQiOiAxNjgzMDAwMDAwLCA
iZXhwIjogMTg4MzAwMDAwMCwgInZjdCI6ICJ1cm46ZXVkaTpwaWQ6ZGU6MSIsICJfc2R
fYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnY
iOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbER
sczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U
2dDRqVDlGMkhaUSJ9fX0.ZOZQTqmq8X1mCyFXi0wbV8xjctX1AlEa5TkdnkKOyWvLfW4
0XDb5oj9tzkgwff5s44IDnrfAdgLtmTcojs97_Q~WyJlSzVvNXBIZmd1cFBwbHRqMXFo
QUp3IiwgImFnZV9lcXVhbF9vcl9vdmVyIiwgeyJfc2QiOiBbIjF0RWl5elBSWU9Lc2Y3
U3NZR01nUFpLc09UMWxRWlJ4SFhBMHI1X0J3a2siLCAiQ1ZLbmx5NVA5MHlKczNFd3R4
UWlPdFVjemFYQ1lOQTRJY3pSYW9ock1EZyIsICJhNDQtZzJHcjhfM0FtSncyWFo4a0kx
eTBRel96ZTlpT2NXMlczUkxwWEdnIiwgImdrdnkwRnV2QkJ2ajBoczJaTnd4Y3FPbGY4
bXUyLWtDRTctTmIyUXh1QlUiLCAiaHJZNEhubUY1YjVKd0M5ZVR6YUZDVWNlSVFBYUlk
aHJxVVhRTkNXYmZaSSIsICJ5NlNGclZGUnlxNTBJYlJKdmlUWnFxalFXejB0TGl1Q21N
ZU8wS3FhekdJIl19XQ~WyJPQktsVFZsdkxnLUFkd3FZR2JQOFpBIiwgIjE4IiwgdHJ1Z
V0~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIm5hdGlvbmFsaXRpZXMiLCBbIkRFI
l1d~eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dCJ9.eyJub25jZSI6ICIxMjM
0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL3ZlcmlmaWVyLmV4YW1wbGUub3JnIiwgIml
hdCI6IDE3NDg1MzcyNDQsICJzZF9oYXNoIjogIlBqTVlmTTA3VmJKZE14TElsdXZSTmI
4OEpGbGpTWDRuLUc0M1VjX0JTUk0ifQ.f3TeS_1BWEG78EbIJRh5wgv8nYumk7euzu6x
gbgpNB4pbQQqgRPWK-vQjlhhgU1EFGZ9LFakFX_0mgul1G_3mw

This is the payload of the matching Key Binding JWT 🧚:

{
  "nonce": "1234567890",
  "aud": "https://verifier.example.org",
  "iat": 1748537244,
  "sd_hash": "PjMYfM07VbJdMxLIluvRNb88JFljSX4n-G43Uc_BSRM"
}

After validation 💅, the Verifier will have this Processed SD-JWT Payload ready to go for further handling 👑:

{
  "iss": "https://pid-issuer.bund.de.example",
  "iat": 1683000000,
  "exp": 1883000000,
  "vct": "urn:eudi:pid:de:1",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc",
      "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"
    }
  },
  "age_equal_or_over": {
    "18": true
  },
  "nationalities": [
    "DE"
  ]
}

A.4. W3C Verifiable Credentials Data Model v2.0 🌈

Okay, this one is non-normative 🙈, so it's just here to show how the stuff in this spec could be used to express a W3C Verifiable Credentials Data Model v2.0 payload [VC_DATA_v2.0]. Lowkey iconic 💜.

Key Binding is applied using the Holder's public key passed in a cnf claim in the SD-JWT 🔑.

Here's the input JWT Claims Set 🍓:

{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://w3id.org/vaccination/v1"
  ],
  "type": [
    "VerifiableCredential",
    "VaccinationCertificate"
  ],
  "issuer": "https://example.com/issuer",
  "issuanceDate": "2023-02-09T11:01:59Z",
  "expirationDate": "2028-02-08T11:01:59Z",
  "name": "COVID-19 Vaccination Certificate",
  "description": "COVID-19 Vaccination Certificate",
  "credentialSubject": {
    "vaccine": {
      "type": "Vaccine",
      "atcCode": "J07BX03",
      "medicinalProductName": "COVID-19 Vaccine Moderna",
      "marketingAuthorizationHolder": "Moderna Biotech"
    },
    "nextVaccinationDate": "2021-08-16T13:40:12Z",
    "countryOfVaccination": "GE",
    "dateOfVaccination": "2021-06-23T13:40:12Z",
    "order": "3/3",
    "recipient": {
      "type": "VaccineRecipient",
      "gender": "Female",
      "birthDate": "1961-08-17",
      "givenName": "Marion",
      "familyName": "Mustermann"
    },
    "type": "VaccinationEvent",
    "administeringCentre": "Praxis Sommergarten",
    "batchNumber": "1626382736",
    "healthProfessional": "883110000015376"
  }
}

And now the issued SD-JWT 🦋:

okay bestie, here's the whole SD-JWT glued together 💅✨ it's a signed JWT first, then a bunch of Disclosures stacked up behind it 🦋

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJAY29udGV4
dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0
cHM6Ly93M2lkLm9yZy92YWNjaW5hdGlvbi92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJs
ZUNyZWRlbnRpYWwiLCAiVmFjY2luYXRpb25DZXJ0aWZpY2F0ZSJdLCAiaXNzdWVyIjog
Imh0dHBzOi8vZXhhbXBsZS5jb20vaXNzdWVyIiwgImlzc3VhbmNlRGF0ZSI6ICIyMDIz
LTAyLTA5VDExOjAxOjU5WiIsICJleHBpcmF0aW9uRGF0ZSI6ICIyMDI4LTAyLTA4VDEx
OjAxOjU5WiIsICJuYW1lIjogIkNPVklELTE5IFZhY2NpbmF0aW9uIENlcnRpZmljYXRl
IiwgImRlc2NyaXB0aW9uIjogIkNPVklELTE5IFZhY2NpbmF0aW9uIENlcnRpZmljYXRl
IiwgImNyZWRlbnRpYWxTdWJqZWN0IjogeyJfc2QiOiBbIjFWX0stOGxEUThpRlhCRlhi
Wlk5ZWhxUjRIYWJXQ2k1VDB5Ykl6WlBld3ciLCAiSnpqTGd0UDI5ZFAtQjN0ZDEyUDY3
NGdGbUsyenk4MUhNdEJnZjZDSk5XZyIsICJSMmZHYmZBMDdaX1lsa3FtTlp5bWExeHl5
eDFYc3RJaVM2QjFZYmwySlo0IiwgIlRDbXpybDdLMmdldl9kdTdwY01JeXpSTEhwLVll
Zy1GbF9jeHRyVXZQeGciLCAiVjdrSkJMSzc4VG1WRE9tcmZKN1p1VVBIdUtfMmNjN3la
UmE0cVYxdHh3TSIsICJiMGVVc3ZHUC1PRERkRm9ZNE5semxYYzN0RHNsV0p0Q0pGNzVO
dzhPal9nIiwgInpKS19lU01YandNOGRYbU1aTG5JOEZHTTA4ekozX3ViR2VFTUotNVRC
eTAiXSwgInZhY2NpbmUiOiB7Il9zZCI6IFsiMWNGNWhMd2toTU5JYXFmV0pyWEk3Tk1X
ZWRMLTlmNlkyUEE1MnlQalNaSSIsICJIaXk2V1d1ZUxENWJuMTYyOTh0UHY3R1hobWxk
TURPVG5CaS1DWmJwaE5vIiwgIkxiMDI3cTY5MWpYWGwtakM3M3ZpOGViT2o5c214M0Mt
X29nN2dBNFRCUUUiXSwgInR5cGUiOiAiVmFjY2luZSJ9LCAicmVjaXBpZW50IjogeyJf
c2QiOiBbIjFsU1FCTlkyNHEwVGg2T0d6dGhxLTctNGw2Y0FheHJZWE9HWnBlV19sbkEi
LCAiM256THE4MU0yb04wNndkdjFzaEh2T0VKVnhaNUtMbWREa0hFREpBQldFSSIsICJQ
bjFzV2kwNkc0TEpybm4tX1JUMFJiTV9IVGR4blBKUXVYMmZ6V3ZfSk9VIiwgImxGOXV6
ZHN3N0hwbEdMYzcxNFRyNFdPN01HSnphN3R0N1FGbGVDWDRJdHciXSwgInR5cGUiOiAi
VmFjY2luZVJlY2lwaWVudCJ9LCAidHlwZSI6ICJWYWNjaW5hdGlvbkV2ZW50In0sICJf
c2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJj
cnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxp
bERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVj
Q0U2dDRqVDlGMkhaUSJ9fX0.OZomvwO8iw4db89MYCeeomBVStXkT6u7G7FkicPWZnd2
_hGgr0l_u1NHgPVocuOt-m32Uu6kwtPmYFxKk0AOeA~WyIyR0xDNDJzS1F2ZUNmR2Zye
U5STjl3IiwgImF0Y0NvZGUiLCAiSjA3QlgwMyJd~WyJlbHVWNU9nM2dTTklJOEVZbnN4
QV9BIiwgIm1lZGljaW5hbFByb2R1Y3ROYW1lIiwgIkNPVklELTE5IFZhY2NpbmUgTW9k
ZXJuYSJd~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgIm1hcmtldGluZ0F1dGhvcml6
YXRpb25Ib2xkZXIiLCAiTW9kZXJuYSBCaW90ZWNoIl0~WyJlSThaV205UW5LUHBOUGV
OZW5IZGhRIiwgIm5leHRWYWNjaW5hdGlvbkRhdGUiLCAiMjAyMS0wOC0xNlQxMzo0MDo
xMloiXQ~WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImNvdW50cnlPZlZhY2NpbmF0
aW9uIiwgIkdFIl0~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImRhdGVPZlZhY2Np
bmF0aW9uIiwgIjIwMjEtMDYtMjNUMTM6NDA6MTJaIl0~WyJQYzMzSk0yTGNoY1VfbEhn
Z3ZfdWZRIiwgIm9yZGVyIiwgIjMvMyJd~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiw
gImdlbmRlciIsICJGZW1hbGUiXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImJi
cnRoRGF0ZSIsICIxOTYxLTA4LTE3Il0~WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwg
ImdpdmVuTmFtZSIsICJNYXJpb24iXQ~WyI1YlBzMUlxdVpOYTBoa2FGenp6Wk53IiwgI
mZhbWlseU5hbWUiLCAiTXVzdGVybWFubiJd~WyI1YTJXMF9OcmxFWnpmcW1rXzdQcS13
IiwgImFkbWluaXN0ZXJpbmdDZW50cmUiLCAiUHJheGlzIFNvbW1lcmdhcnRlbiJd~WyJ
5MXNWVTV3ZGZKYWhWZGd3UGdTN1JRIiwgImJhdGNoTnVtYmVyIiwgIjE2MjYzODI3MzY
iXQ~WyJIYlE0WDhzclZXM1FEeG5JSmRxeU9BIiwgImhlYWx0aFByb2Zlc3Npb25hbCIs
ICI4ODMxMTAwMDAwMTUzNzYiXQ~

Next up is the payload that the SD-JWT is built from 🎀 It's giving very real credential vibes, complete with _sd digests standing in for the hidden claims 🌸

{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://w3id.org/vaccination/v1"
  ],
  "type": [
    "VerifiableCredential",
    "VaccinationCertificate"
  ],
  "issuer": "https://example.com/issuer",
  "issuanceDate": "2023-02-09T11:01:59Z",
  "expirationDate": "2028-02-08T11:01:59Z",
  "name": "COVID-19 Vaccination Certificate",
  "description": "COVID-19 Vaccination Certificate",
  "credentialSubject": {
    "_sd": [
      "1V_K-8lDQ8iFXBFXbZY9ehqR4HabWCi5T0ybIzZPeww",
      "JzjLgtP29dP-B3td12P674gFmK2zy81HMtBgf6CJNWg",
      "R2fGbfA07Z_YlkqmNZyma1xyyx1XstIiS6B1Ybl2JZ4",
      "TCmzrl7K2gev_du7pcMIyzRLHp-Yeg-Fl_cxtrUvPxg",
      "V7kJBLK78TmVDOmrfJ7ZuUPHuK_2cc7yZRa4qV1txwM",
      "b0eUsvGP-ODDdFoY4NlzlXc3tDslWJtCJF75Nw8Oj_g",
      "zJK_eSMXjwM8dXmMZLnI8FGM08zJ3_ubGeEMJ-5TBy0"
    ],
    "vaccine": {
      "_sd": [
        "1cF5hLwkhMNIaqfWJrXI7NMWedL-9f6Y2PA52yPjSZI",
        "Hiy6WWueLD5bn16298tPv7GXhmldMDOTnBi-CZbphNo",
        "Lb027q691jXXl-jC73vi8ebOj9smx3C-_og7gA4TBQE"
      ],
      "type": "Vaccine"
    },
    "recipient": {
      "_sd": [
        "1lSQBNY24q0Th6OGzthq-7-4l6cAaxrYXOGZpeW_lnA",
        "3nzLq81M2oN06wdv1shHvOEJVxZ5KLmdDkHEDJABWEI",
        "Pn1sWi06G4LJrnn-_RT0RbM_HTdxnPJQuX2fzWv_JOU",
        "lF9uzdsw7HplGLc714Tr4WO7MGJza7tt7QFleCX4Itw"
      ],
      "type": "VaccineRecipient"
    },
    "type": "VaccinationEvent"
  },
  "_sd_alg": "sha-256",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc",
      "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"
    }
  }
}

Now the digests in the payload point at these Disclosures, and I'm going to go through them one by one because it's so satisfying 🦋🫶

Claim atcCode 💜

SHA-256 Hash:

1cF5hLwkhMNIaqfWJrXI7NMWedL-9f6Y2PA52yPjSZI

Disclosure:

WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImF0Y0NvZGUiLCAiSjA3QlgwMyJd

Contents:

["2GLC42sKQveCfGfryNRN9w", "atcCode", "J07BX03"]

Claim medicinalProductName 🌈

SHA-256 Hash:

Hiy6WWueLD5bn16298tPv7GXhmldMDOTnBi-CZbphNo

Disclosure:

WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgIm1lZGljaW5hbFByb2R1Y3ROYW1lIiwgIkNPVklELTE5IFZhY2NpbmUgTW9kZXJuYSJd

Contents:

["eluV5Og3gSNII8EYnsxA_A", "medicinalProductName", "COVID-19 Vaccine Moderna"]

Claim marketingAuthorizationHolder ✨

SHA-256 Hash:

Lb027q691jXXl-jC73vi8ebOj9smx3C-_og7gA4TBQE

Disclosure:

WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgIm1hcmtldGluZ0F1dGhvcml6YXRpb25Ib2xkZXIiLCAiTW9kZXJuYSBCaW90ZWNoIl0

Contents:

["6Ij7tM-a5iVPGboS5tmvVA", "marketingAuthorizationHolder", "Moderna Biotech"]

Claim nextVaccinationDate 🎀

SHA-256 Hash:

R2fGbfA07Z_YlkqmNZyma1xyyx1XstIiS6B1Ybl2JZ4

Disclosure:

WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgIm5leHRWYWNjaW5hdGlvbkRhdGUiLCAiMjAyMS0wOC0xNlQxMzo0MDoxMloiXQ

Contents:

["eI8ZWm9QnKPpNPeNenHdhQ", "nextVaccinationDate", "2021-08-16T13:40:12Z"]

Claim countryOfVaccination 🍓

SHA-256 Hash:

JzjLgtP29dP-B3td12P674gFmK2zy81HMtBgf6CJNWg

Disclosure:

WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgImNvdW50cnlPZlZhY2NpbmF0aW9uIiwgIkdFIl0

Contents:

["Qg_O64zqAxe412a108iroA", "countryOfVaccination", "GE"]

Claim dateOfVaccination 💅

SHA-256 Hash:

zJK_eSMXjwM8dXmMZLnI8FGM08zJ3_ubGeEMJ-5TBy0

Disclosure:

WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImRhdGVPZlZhY2NpbmF0aW9uIiwgIjIwMjEtMDYtMjNUMTM6NDA6MTJaIl0

Contents:

["AJx-095VPrpTtN4QMOqROA", "dateOfVaccination", "2021-06-23T13:40:12Z"]

Claim order 🧚

SHA-256 Hash:

b0eUsvGP-ODDdFoY4NlzlXc3tDslWJtCJF75Nw8Oj_g

Disclosure:

WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgIm9yZGVyIiwgIjMvMyJd

Contents:

["Pc33JM2LchcU_lHggv_ufQ", "order", "3/3"]

Claim gender 🌸

SHA-256 Hash:

3nzLq81M2oN06wdv1shHvOEJVxZ5KLmdDkHEDJABWEI

Disclosure:

WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImdlbmRlciIsICJGZW1hbGUiXQ

Contents:

["G02NSrQfjFXQ7Io09syajA", "gender", "Female"]

Claim birthDate 🦋

SHA-256 Hash:

Pn1sWi06G4LJrnn-_RT0RbM_HTdxnPJQuX2fzWv_JOU

Disclosure:

WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImJpcnRoRGF0ZSIsICIxOTYxLTA4LTE3Il0

Contents:

["lklxF5jMYlGTPUovMNIvCA", "birthDate", "1961-08-17"]

Claim givenName 💜

SHA-256 Hash:

lF9uzdsw7HplGLc714Tr4WO7MGJza7tt7QFleCX4Itw

Disclosure:

WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgImdpdmVuTmFtZSIsICJNYXJpb24iXQ

Contents:

["nPuoQnkRFq3BIeAm7AnXFA", "givenName", "Marion"]

Claim familyName 👑

SHA-256 Hash:

1lSQBNY24q0Th6OGzthq-7-4l6cAaxrYXOGZpeW_lnA

Disclosure:

WyI1YlBzMUlxdVpOYTBoa2FGenp6Wk53IiwgImZhbWlseU5hbWUiLCAiTXVzdGVybWFubiJd

Contents:

["5bPs1IquZNa0hkaFzzzZNw", "familyName", "Mustermann"]

Claim administeringCentre 🌈

SHA-256 Hash:

TCmzrl7K2gev_du7pcMIyzRLHp-Yeg-Fl_cxtrUvPxg

Disclosure:

WyI1YTJXMF9OcmxFWnpmcW1rXzdQcS13IiwgImFkbWluaXN0ZXJpbmdDZW50cmUiLCAiUHJheGlzIFNvbW1lcmdhcnRlbiJd

Contents:

["5a2W0_NrlEZzfqmk_7Pq-w", "administeringCentre", "Praxis Sommergarten"]

Claim batchNumber 🍓

SHA-256 Hash:

V7kJBLK78TmVDOmrfJ7ZuUPHuK_2cc7yZRa4qV1txwM

Disclosure:

WyJ5MXNWVTV3ZGZKYWhWZGd3UGdTN1JRIiwgImJhdGNoTnVtYmVyIiwgIjE2MjYzODI3MzYiXQ

Contents:

["y1sVU5wdfJahVdgwPgS7RQ", "batchNumber", "1626382736"]

Claim healthProfessional ✨

SHA-256 Hash:

1V_K-8lDQ8iFXBFXbZY9ehqR4HabWCi5T0ybIzZPeww

Disclosure:

WyJIYlE0WDhzclZXM1FEeG5JSmRxeU9BIiwgImhlYWx0aFByb2Zlc3Npb25hbCIsICI4ODMxMTAwMDAwMTUzNzYiXQ

Contents:

["HbQ4X8srVW3QDxnIJdqyOA", "healthProfessional", "883110000015376"]

This is an example of an SD-JWT+KB that discloses only type, medicinalProductName, atcCode of the vaccine, type of the recipient, type, order, and dateOfVaccination:

Okay bestie, the examples are giving everything 💖

The encoded SD-JWT below is the full example token. Its signature and disclosures are real, so you can validate them yourself 🔥

eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJAY29udGV4
dCI6IFsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiLCAiaHR0
cHM6Ly93M2lkLm9yZy92YWNjaW5hdGlvbi92MSJdLCAidHlwZSI6IFsiVmVyaWZpYWJs
ZUNyZWRlbnRpYWwiLCAiVmFjY2luYXRpb25DZXJ0aWZpY2F0ZSJdLCAiaXNzdWVyIjog
Imh0dHBzOi8vZXhhbXBsZS5jb20vaXNzdWVyIiwgImlzc3VhbmNlRGF0ZSI6ICIyMDIz
LTAyLTA5VDExOjAxOjU5WiIsICJleHBpcmF0aW9uRGF0ZSI6ICIyMDI4LTAyLTA4VDEx
OjAxOjU5WiIsICJuYW1lIjogIkNPVklELTE5IFZhY2NpbmF0aW9uIENlcnRpZmljYXRl
IiwgImRlc2NyaXB0aW9uIjogIkNPVklELTE5IFZhY2NpbmF0aW9uIENlcnRpZmljYXRl
IiwgImNyZWRlbnRpYWxTdWJqZWN0IjogeyJfc2QiOiBbIjFWX0stOGxEUThpRlhCRlhi
Wlk5ZWhxUjRIYWJXQ2k1VDB5Ykl6WlBld3ciLCAiSnpqTGd0UDI5ZFAtQjN0ZDEyUDY3
NGdGbUsyenk4MUhNdEJnZjZDSk5XZyIsICJSMmZHYmZBMDdaX1lsa3FtTlp5bWExeHl5
eDFYc3RJaVM2QjFZYmwySlo0IiwgIlRDbXpybDdLMmdldl9kdTdwY01JeXpSTEhwLVll
Zy1GbF9jeHRyVXZQeGciLCAiVjdrSkJMSzc4VG1WRE9tcmZKN1p1VVBIdUtfMmNjN3la
UmE0cVYxdHh3TSIsICJiMGVVc3ZHUC1PRERkRm9ZNE5semxYYzN0RHNsV0p0Q0pGNzVO
dzhPal9nIiwgInpKS19lU01YandNOGRYbU1aTG5JOEZHTTA4ekozX3ViR2VFTUotNVRC
eTAiXSwgInZhY2NpbmUiOiB7Il9zZCI6IFsiMWNGNWhMd2toTU5JYXFmV0pyWEk3Tk1X
ZWRMLTlmNlkyUEE1MnlQalNaSSIsICJIaXk2V1d1ZUxENWJuMTYyOTh0UHY3R1hobWxk
TURPVG5CaS1DWmJwaE5vIiwgIkxiMDI3cTY5MWpYWGwtakM3M3ZpOGViT2o5c214M0Mt
X29nN2dBNFRCUUUiXSwgInR5cGUiOiAiVmFjY2luZSJ9LCAicmVjaXBpZW50IjogeyJf
c2QiOiBbIjFsU1FCTlkyNHEwVGg2T0d6dGhxLTctNGw2Y0FheHJZWE9HWnBlV19sbkEi
LCAiM256THE4MU0yb04wNndkdjFzaEh2T0VKVnhaNUtMbWREa0hFREpBQldFSSIsICJQ
bjFzV2kwNkc0TEpybm4tX1JUMFJiTV9IVGR4blBKUXVYMmZ6V3ZfSk9VIiwgImxGOXV6
ZHN3N0hwbEdMYzcxNFRyNFdPN01HSnphN3R0N1FGbGVDWDRJdHciXSwgInR5cGUiOiAi
VmFjY2luZVJlY2lwaWVudCJ9LCAidHlwZSI6ICJWYWNjaW5hdGlvbkV2ZW50In0sICJf
c2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJj
cnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxp
bERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVj
Q0U2dDRqVDlGMkhaUSJ9fX0.OZomvwO8iw4db89MYCeeomBVStXkT6u7G7FkicPWZnd2
_hGgr0l_u1NHgPVocuOt-m32Uu6kwtPmYFxKk0AOeA~WyJQYzMzSk0yTGNoY1VfbEhnZ
3ZfdWZRIiwgIm9yZGVyIiwgIjMvMyJd~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwg
ImRhdGVPZlZhY2NpbmF0aW9uIiwgIjIwMjEtMDYtMjNUMTM6NDA6MTJaIl0~WyIyR0xD
NDJzS1F2ZUNmR2ZyeU5STjl3IiwgImF0Y0NvZGUiLCAiSjA3QlgwMyJd~WyJlbHVWNU9
nM2dTTklJOEVZbnN4QV9BIiwgIm1lZGljaW5hbFByb2R1Y3ROYW1lIiwgIkNPVklELTE
5IFZhY2NpbmUgTW9kZXJuYSJd~eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImtiK2p3dC
J9.eyJub25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL3ZlcmlmaWVyL
mV4YW1wbGUub3JnIiwgImlhdCI6IDE3NDg1MzcyNDQsICJzZF9oYXNoIjogIklvV1VIO
TFsbGYzWEVybDQyYlEzc3hfNTNWMW8xdWpDejA4aERxSEs3RGsifQ.n0vzyIwCFMDVau
EaeJIWEKZZchxXMpXTQewHgAkARbOSZxB09IbXXtHfpoGqO_BtNFN2lShJEIQBGyc-Xp
HigA

Once the Verifier has validated everything, it gets the Processed SD-JWT Payload to work with. Here's what that looks like 🎀

{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://w3id.org/vaccination/v1"
  ],
  "type": [
    "VerifiableCredential",
    "VaccinationCertificate"
  ],
  "issuer": "https://example.com/issuer",
  "issuanceDate": "2023-02-09T11:01:59Z",
  "expirationDate": "2028-02-08T11:01:59Z",
  "name": "COVID-19 Vaccination Certificate",
  "description": "COVID-19 Vaccination Certificate",
  "credentialSubject": {
    "vaccine": {
      "type": "Vaccine",
      "atcCode": "J07BX03",
      "medicinalProductName": "COVID-19 Vaccine Moderna"
    },
    "recipient": {
      "type": "VaccineRecipient"
    },
    "type": "VaccinationEvent",
    "order": "3/3",
    "dateOfVaccination": "2021-06-23T13:40:12Z"
  },
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc",
      "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"
    }
  }
}

A.5. Elliptic Curve Key Used in the Examples

Here's the Elliptic Curve public key, in JWK format 🦋 You can use it to validate the Issuer signatures in all the examples above:

{
  "kty": "EC",
  "crv": "P-256",
  "x": "b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ",
  "y": "Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8"
}

And the public key for validating a Key Binding JWT? That one lives in the examples as the content of the cnf claim 🔑

Appendix B. Disclosure Format Considerations

Okay so, Section 4.2 explains that a Disclosure is JSON with a salt and the cleartext content of a claim, and that whole thing gets base64url encoded 🌸 That encoded value is what you feed in to calculate the digest for that claim. Putting the digest in the signed JWT is what keeps the claim value's integrity intact. Using the encoded content as the integrity input is conceptually similar to what JWS does, and it's especially handy when the content, like JSON, can look different but still mean exactly the same thing. That way there's no canonicalization needed 💅 Let's dig into why this design choice matters.

When a Verifier receives an SD-JWT, it must be able to recompute digests of the disclosed claim values. Given the same input values, it has to get the same digest values the Issuer signed 🔥

Usually, JSON-based formats carry claim values as plain properties of a JSON object, like this:

...
  "family_name": "Möbius",
  "address": {
    "street_address": "Schulstr. 12",
    "locality": "Schulpforta"
  }
...

But here's the catch 😭 Whenever you need to do computation over the data, like signing or computing digests, things get messy. Common signature schemes need the exact same byte string as input for verification that was used to create the signature. The digest approach has the same problem: for the Issuer and the Verifier to land on the same digest, they have to hash the exact same byte string.

JSON doesn't prescribe one unique encoding for data, though. It allows variations in the encoded string. The data above, for example, can be encoded as

...
"family_name": "M\u00f6bius",
"address": {
  "street_address": "Schulstr. 12",
  "locality": "Schulpforta"
}
...

or as

...
"family_name": "Möbius",
"address": {"locality":"Schulpforta", "street_address":
   "Schulstr. 12"}
...

The two versions of family_name are super different at the byte level, but they produce equivalent objects. The same goes for address, which varies in whitespace and in the order of the object's elements 🧚

Variations in whitespace, object property order, and Unicode character encoding are all allowed by the JSON specification. That includes other variations too, like floating-point numbers, as described in [RFC8785]. Variations can sneak in whenever JSON data gets serialized or deserialized. If you don't deal with them, you end up with different digests and signatures that won't verify 😭

There are generally two ways to handle this:

  1. Canonicalization: The data is transferred in JSON format, which can introduce variations in its representation. Before a digest is computed, though, it gets transformed into a canonical form. Both the Issuer and the Verifier must use the same canonicalization algorithm so they arrive at the same byte string for computing a digest.
  2. Source string hardening: Instead of transferring data in a format that can introduce variations, a representation of the data is serialized. That representation is used as the hashing input at the Verifier, and it's also transferred to the Verifier and used for the same digest calculation there. This means the Verifier can compute and check the digest of the byte string easily before it finally deserializes and accesses the data.

Mixed approaches are possible too, meaning you transfer both the original JSON data and a string suitable for computing a digest. But those can easily lead to undetected inconsistencies, which become time-of-check-time-of-use type security vulnerabilities. Yikes 🙈

This specification goes with source string hardening, because it gives simple and reliable interoperability without needing a canonicalization library. To harden the source string, any serialization format that supports the necessary data types could work in theory, like protobuf, msgpack, or pickle. In this specification, though, the format is JSON, and the plaintext contents of each Disclosure are base64url encoded for transport. The payoff is that SD-JWTs can be implemented purely with widely available JWT, JSON, and Base64 encoding and decoding libraries 👑

A Verifier can check the digest over the source string before it extracts the original JSON data. Variations in the encoding of the source string are implicitly tolerated by the Verifier, since the digest is computed over a predefined byte string and not over a JSON object ✨

Heads up: Disclosures are neither intended nor suitable for direct consumption by an application that needs the disclosed claim values after the Verifier has verified them. They're only meant for the Verifier to check the digests over the source strings and pull out the original JSON data. The application then uses that original JSON data. See Section 7.3 for details.

Acknowledgements

We would like to thank Alen Horvat, Alex Hodder, Anders Rundgren, Arjan Geluk, Chad Parry, Christian Bormann, Christian Paquin, Dale Bowie, Dan Moore, David Bakker, David Waite, Deb Cooley, Fabian Hauck, Filip Skokan, Giuseppe De Marco, Jacob Ward, Jeffrey Yasskin, John Preuß Mattsson, Joseph Heenan, Justin Richer, Kushal Das, Martin Thomson, Matthew Miller, Michael Fraser, Michael B. Jones, Mike Prorock, Nat Sakimura, Neil Madden, Oliver Terbu, Orie Steele, Paul Bastian, Peter Altmann, Pieter Kasselman, Richard Barnes, Rohan Mahy, Roman Danyliw, Ryosuke Abe, Sami Rosendahl, Shawn Butterfield, Shawn Emery, Simon Schulz, Takahiko Kawasaki, Tobias Looker, Torsten Lodderstedt, Vittorio Bertocci, Watson Ladd, and Yaron Sheffer for their contributions (some of which were substantial) to this document and to the initial set of implementations 🫶

The work on this document was started at the OAuth Security Workshop 2022 in Trondheim, Norway. 🌈

Authors' Addresses

Daniel Fett Authlete Email: mail@danielfett.de URI: https://danielfett.de/

Kristina Yasuda Keio University Email: kristina@sfc.keio.ac.jp

Brian Campbell Ping Identity Email: bcampbell@pingidentity.com

📦 where this page comes from