SD-JWT (選択的開示のためのデータフォーマット)

# はじめに

[EIC 2023](https://www.kuppingercole.com/events/eic2023 "European Identity and Cloud Conference 2023") (2023年5月9日〜5月12日/ドイツ・ベルリン) のオープニングワークショップにおいて、[OpenWallet Foundation](https://openwallet.foundation/)は、[Dr. Daniel Fett](https://www.linkedin.com/in/dr-fett/) (ダニエル・フェット博士) が**SD-JWT**のリファレンス実装のコード (Python) を同団体にコントリビュートしたと発表しました。同団体にとって最初のコード・コントリビューションです。実際のコードはGitHubのdanielfett/[sd-jwt](https://github.com/danielfett/sd-jwt)に置かれています。

|![EIC2023_OWF_Workshop_Daniel.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/785ee337-7447-466e-32b4-8d59fe7e79a4.jpeg)|
|:--|
|EIC 2023のOWFワークショップに登壇するダニエル|

:::note info
[プレゼン資料](https://danielfett.de/download/sd-jwt-library-eic-2023.pdf)はダニエルのウェブサイトの投稿 "[SD-JWT Reference Implementation at the OpenWalletFoundation (OWF)](https://danielfett.de/talks/2023-05-09-sd-jwt-library-at-eic/)" で公開されています。彼は2023年4月から[Authlete社](https://www.authlete.com/ja/)のメンバーになったのでプレゼン資料にAuthleteのロゴが載っています。
:::

SD-JWTは、**Selective Disclosure** (選択的開示) を実現するためのフォーマットの一つです。Camenisch-Lysyanskaya Signatures (論文:[Signature Schemes and Anonymous Credentials from Bilinear Maps](https://iacr.org/archive/crypto2004/31520055/cl04.pdf)) など、競合する仕様は幾つかあるものの、2022年に行われた[クレデンシャル・プロファイル比較](https://github.com/vcstuff/credential-profile-comparison) ([比較表](https://docs.google.com/spreadsheets/d/1Z4cYfjbbE-rABcfC-xab8miocKLomivYMUFibOh9BVo/edit#gid=1590639334)) や[IDUnion](https://idunion.org/)のハッカソンの経験を経て、SD-JWTを推進する流れができつつあります。例えば、[European Digital Identity](https://commission.europa.eu/strategy-and-policy/priorities-2019-2024/europe-fit-digital-age/european-digital-identity_en)はEuropean Union Digital Identity Wallet (EUDI Wallet) のArchitecture and Reference Framework (ARF) として示した[文書](https://github.com/eu-digital-identity-wallet/architecture-and-reference-framework/blob/main/arf.md)において、SD-JWTを用いたSelective Disclosureの実現を必須としています。

本記事ではSD-JWTの解説を行います。

|![EIC2023_SD-JWT_Daniel_Kristina_Joseph.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/5b05b8c0-df9a-74c5-834f-d587b1cddf85.jpeg)|
|:--|
|SD-JWTのTシャツを着て写真に写る[ダニエル](https://www.linkedin.com/in/dr-fett/)、[クリスチーナ](https://www.linkedin.com/in/kristina-yasuda-6263b5a2/) (両者はSD-JWT仕様の共同著者)、[ジョセフ](https://www.linkedin.com/in/josephheenan/) (Authlete社CTO)。(ジョセフの[ツイート](https://twitter.com/josephheenan/status/1656619854089035776)より)<br><sub>(EIC 2023のイベント会場、楽しそうだな...)</sub>|

# 解決したい課題

[W3C Verifiable Credential Data Model](https://www.w3.org/TR/vc-data-model/) ([日本語訳](http://www.asahi-net.or.jp/~ax2s-kmtn/internet/did/REC-vc-data-model-20220303.html)) が示すエコシステムでは、**Issuer** (発行者) が**Holder** (保有者) に対して**Verifiable Credential** (検証可能な資格証明) (以降VC) を発行します。

```plantuml
left to right direction
agent Issuer
agent Holder
Issuer-->Holder : Verifiable Credential
```

その後、Holderは、Verifiable Credentialの内容の全てまたは一部を、**Verifiable Presentation** (検証可能なプレゼンテーション) という形で**Verifier** (検証者) に渡します。

```plantuml
left to right direction
agent Holder
agent Verifier
Holder-->Verifier : Verifiable Presentation
```

IssuerはVCを発行する際、データにデジタル署名を付けます。署名のおかげで、当VCが当Issuerから発行されたものであることを検証でき、VCのデータが改竄されていればその事実を検出することもできます。

では、HolderがVCの内容の一部のみをVerifierに渡したい場合、何が起こるでしょうか。

一部のみということは、VCの内容に手を加える必要があるということです。しかし、手を加えてしまうと、署名が無効になってしまいます。これでは、加工後のデータが当Issuerよって発行されたデータに基づくものなのか検証できず、改竄を検出することもできません。

「データ加工後も、元データはIssuerが発行したものであることを検証可能にし、改竄も検出したい」というのが解きたい問題です。

# 基本的なアイディア

通常、データ集合に対して署名を行う場合、まずはデータ名・データ値の組を含むデータ集合を用意します。

```plantuml
@startjson
{
  "データ名1": "データ値1",
  "データ名2": "データ値2",
  "データ名3": "データ値3"
}
@endjson
```

そして、その集合全体を入力としてIssuerが署名します。

一方、SD-JWTでは、選択的開示の対象としたいデータをデータ集合から切り離し、

```plantuml
@startjson
{
  "": {"データ名1": "データ値1"},
  "データ名2": "データ値2",
  "データ名3": "データ値3"
}
@endjson
```

かわりに、切り離したデータのダイジェスト (ハッシュ値) を元のデータ集合に埋め込みます。

```plantuml
@startjson
{
  "ダイジェスト1": {"データ名1": "データ値1"},
  "データ名2": "データ値2",
  "データ名3": "データ値3"
}
@endjson
```

そして、通常データとダイジェストを含むデータ集合を入力としてIssuerが署名します。

その後、署名付きデータ集合と切り離したデータを一緒にVerifierに渡します。このとき、切り離したデータを一緒に渡さなければ「当該データを開示しない」という意味になります。重要な点は、切り離したデータを一緒に渡しても渡さなくても、データ集合に対する署名は有効であることです。

# SD-JWT仕様

## 署名

データに署名を付ける方法は幾つかありますが、SD-JWTでは、名前が示す通り、JWT ([RFC 7519](https://www.rfc-editor.org/rfc/rfc7519.html)) を活用して署名をおこないます。

## ダイジェストの計算

SD-JWTにおいて、JWTのペイロード部分が「基本的なアイディア」におけるデータ集合にあたります。

```json
{
  "クレーム名1": "クレーム値1",
  "クレーム名2": "クレーム値2",
  "クレーム名3": "クレーム値3"
}
```

ここから選択的開示の対象としたいクレーム名・クレーム値の組を抜き出し、

```json
  "クレーム名1": "クレーム値1"
```

そのダイジェストを計算すれば、それがデータ集合に埋め込むべきダイジェストとなります。

ただし、このような単純な処理にしてしまうと、ダイジェストの元となったデータを推測される恐れがあるので、データ名とデータ値にランダムに生成したソルトも加えた上でダイジェストを計算します。

SD-JWT仕様で定義されている実際の手順は次の通りです。

:one: ランダムに生成したソルト(文字列)、クレーム名(文字列)、クレーム値(JSONで有効な任意の型)、を要素として持つJSON配列を用意する (要素間等に余計な空白文字が入っていても問題ない)。

```json
["ソルト","クレーム名",クレーム値]
```

:two: そのJSON配列をUTF-8エンコーディングのバイト配列に変換する。

:three: そのバイト配列をbase64urlでエンコーディングする。生成されたbase64url文字列を『**Disclosure**』(ディスクロージャー) と呼ぶ。

:four: そのDisclosureのダイジェストを計算する。デフォルトのダイジェストアルゴリズムは`sha-256` (参考: [IANA Named Information Hash Algorithm Registry](https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg))。

具体例を見てみましょう。

ランダムに生成したソルト、クレーム名`family_name`、クレーム値`Möbius`でJSON配列を作ります。この例では、要素間に空白文字が1つずつ入っています (ダイジェストの計算結果に影響します)。

```json:Disclosureの元となるJSON配列
["_26bc4LT-ac6q2KI6cBW5es", "family_name", "Möbius"]
```

このJSON配列をUTF-8エンコーディングでバイト配列にし、base64urlでエンコーディングします。

```:Disclosure
WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsICJmYW1pbHlfbmFtZSIsICJNw7ZiaXVzIl0
```

このbase64url文字列のダイジェストは、アルゴリズムに`sha-256`を用いると次の値となります。

```:Disclosureのダイジェスト
X9yH0Ajrdm1Oij4tWso9UzzKJvPoDxwmuEcO3XAdRC0
```

## ダイジェストの埋め込み方

計算したダイジェストを元のペイロード部に埋め込む際は、ペイロード部に`_sd`というJSON配列を作り、その配列の要素としてダイジェストを列挙します。

例えば、元々のデータ集合が次のようなものであったとき、

```json
{
  "family_name": "Möbius",
  "given_name": "August"
}
```

`family_name`クレームを選択的開示の対象としたければ、用意するペイロード部は次のようなものになります。

```json
{
  "_sd": [
    "X9yH0Ajrdm1Oij4tWso9UzzKJvPoDxwmuEcO3XAdRC0"
  ],
  "given_name": "August"
}
```

`family_name`も`given_name`も両方とも選択的開示の対象とする場合は、`_sd`配列にダイジェストが2つ含まることになります。

```json
{
  "_sd": [
    "0vYHikNjq7uMSvCVxChXbl2G1ZXBR1vGEs1_F9aqgc8",
    "c8-r36TZc8c8B5dtxWKl3jHzffSdMYp8mcJQaYwvXSA"
  ]
}
```

なお、`_sd`配列内の要素の順番は元データの順番に合わせる必要はありません。むしろ、元データの順番とは無関係な順番としなければなりません。以下はSD-JWT仕様書からの抜粋です。

> _**The Issuer MUST hide the original order of the claims in the array.** To ensure this, it is RECOMMENDED to shuffle the array of hashes, e.g., by sorting it alphanumerically or randomly, after potentially adding decoy digests as described in Section 5.6. The precise method does not matter as long as it does not depend on the original order of elements._

さらには、SD-JWT仕様は、ダミーのダイジェスト (仕様書では『Decoy Digest』と呼んでいる) を配列に混ぜることも推奨しています。

## ネストしたデータの場合

[OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html)の[5.1.1. Address Claim](https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim)で定義されている`address`クレームのように、ネストした値を持つクレームがあります。

```json
{
  "address": {
    "street_address": "Schulstr. 12",
    "locality": "Schulpforta",
    "region": "Sachsen-Anhalt",
    "country": "DE"
  }
}
```

このようなクレームを選択的開示の対象としたい場合、選択肢は二つあります。

一つは、値全体のDisclosureからダイジェストを計算する方法です。

```json
{
  "_sd": [
    "FphFFpj1vtr0rpYK-14fickGKMg3zf1fIpJXxTK8PAE"
  ]
}
```

もう一つは、ネスト内の個々の要素毎にDisclosureを作成し、それぞれのダイジェストを計算する方法です。この場合、`_sd`配列はネスト構造の内部に配置することになります。

```json
{
  "address": {
    "_sd": [
      "G_FeM1D-U3tDJcHB7pwTNEElLal9FE9PUs0klHgeM1c",
      "KlG6HEM6XWbymEJDfyDY4klJkQQ9iTuNG0LQXnE9mQ0",
      "X96Emv4S9uzFUGkU8MmOlFzUwEtDNeT-ToXw3Fx9AfI",
      "ffPGyxFBnNA1r60g2f796Hqq3dBGtaOogpnIBgRGdyY"
    ]
  }
}
```

後者の方法を用いると、ネスト内の個々の要素毎に開示するか否かを選択することができるようになります。

## 配列データの場合

データの値が配列の場合、

```json
{
  "fruits": ["apple", "banana", "cherry"]
}
```

配列全体のDisclosureの元となるJSONを用意し、

```json:Disclosureの元となるJSON配列
["L_6JuVSAxUDQV6cJnUTlKg","fruits",["apple","banana","cherry"]]
```

Discloureを求め、

```:Disclosure
WyJMXzZKdVZTQXhVRFFWNmNKblVUbEtnIiwiZnJ1aXRzIixbImFwcGxlIiwiYmFuYW5hIiwiY2hlcnJ5Il1d
```

ダイジェストを計算して`_sd`配列に埋め込むことができます。

```json
{
  "_sd": [
    "QRvhn5oERVVbke5O22hsbRT4AV6hMopp1m0SIGvv-fo"
  ]
}
```

ただし、この方法では配列内の要素を個別に選択的開示の対象にすることはできません。

各要素を選択的開示の対象としたい場合、次の手順により、対象とする要素を特別な形式のJSONオブジェクトで置き換えます。

:one: ランダムに生成したソルト(文字列)と要素値(JSONで有効な任意の型)を要素として持つJSON配列を用意する (要素間等に余計な空白文字が入っていても問題ない)。(:warning:"クレーム名"がないことに注意)

```json
["ソルト",要素値]
```

:two: そのJSON配列をUTF-8エンコーディングのバイト配列に変換する。

:three: そのバイト配列をbase64urlでエンコーディングする。生成されたbase64url文字列が、当該配列要素用のDisclosureとなる。

:four: そのDisclosureのダイジェストを計算する。

:five: JSONオブジェクトを作成し、プロパティー名に `...` (文字通りにドット三つ)、プロパティー値に:four:で計算したダイジェストを持つプロパティーを追加する。

:six: 対象となる配列要素を:five:で作成したJSONオブジェクトで置き換える。

具体例を見てみましょう。 (仕様書より例を抜粋)

次の配列があるとします。

```json
{
  "nationalities": ["DE", "FR"]
}
```

2番目の要素用のDisclosureを次の手順で用意します。

```json:base64urlエンコード前のDisclosure
["lklxF5jMYlGTPUovMNIvCA", "FR"]
```

```:base64urlエンコード後のDisclosure
WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIkZSIl0
```

Disclosureのダイジェストを計算します。

```:Disclosureのダイジェスト
w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs
```

プロパティー名に`...`、プロパティー値にダイジェストを持つプロパティーだけを含むJSONオブジェクトを用意します。

```json:配列要素を置き換えるための特殊なJSONオブジェクト
{"...": "w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs"}
```

元々の配列要素を上記手順で作成したJSONオブジェクトで置き換えます。

```json:選択的開示可能になった要素を含む配列
{
  "nationalities": ["DE", {"...": "w0I8EKcdCtUPkGCNUrfwVp2xEgNjtoIDlOxc9-PlOhs"}]
}
```

## コラム: 'Simple' is a feature

:::note info
<b>SD-JWT仕様は意図的にシンプルにしています。</b>これは、[IDUnion](https://idunion.org/?lang=en)のハッカソンでの苦い経験から得た教訓だそうです。Selective Disclosureの実現方法は幾つかありえますが、一見良さそうな方法に見えても、マイナーなアルゴリズムのせいでライブラリが存在しない、HSMがサポートしていないので商用展開できない、などの多くの問題にぶち当たったとのことです。そのため、署名アルゴリズムに依存せずにどの開発言語でも容易に実装可能な方法を考案する必要性を痛感したそうです。
:::

|![EIC2023_SD-JWT_Simple_is_a_feature.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/a8bf258a-166a-f6c1-e286-758cad26435f.jpeg)|
|:--|
|「'Simple' is a feature.」について語るダニエル (ジョセフの[ツイート](https://twitter.com/josephheenan/status/1656612067523063808)より)|

## ダイジェストアルゴリズムの指定

ダイジェストの計算にデフォルトの`sha-256`以外のアルゴリズムを用いることもできます。ただし、その際は、使用したアルゴリズムの名前を`_sd_alg`プロパティで示す必要があります。

```json
{
  "_sd": [
    "dTqze-rbn5ARyRc48DqmKl1bMRjxZ4HHoBxpomKYFbWAny_oCz1qrMeFHf6-VleYSRO8ZQHgba8v1KfqL59DYw",
    "vTtR2FYR95Jvezi2uO3idum_Cj6nnT8kB5YlI9dAi-7PFfULxWUz6-8yhPaRq0H712zsFa4fuO8_N17aZDO5VQ"
  ],
  "_sd_alg": "sha-512"
}
```

## 発行時のフォーマット

IssuerがHolderにVCを発行する際、SD-JWTを利用する方法では、0個以上の選択的開示可能な要素をペイロード部に含むJWTと、関連するDisclosure群全てをHolderに渡します。ここでは、当該JWTのことをCredential JWTと呼ぶことにします。

```plantuml
left to right direction
agent Issuer
agent Holder
Issuer-->Holder : Credential-JWT + Disclosure群
```

具体的には、Credential JWTとDisclosure群をチルダ(`~`)で連結して一つの文字列としてから渡します。文字列の末尾にも`~`をつけます。

```html
<Credential-JWT>~<Disclosure1>~...~<DisclosureN>~
```

この文字列全体のことを『**SD-JWT**』と呼びます。

具体例を見てみましょう。

元となるデータ集合が次のものだったとします。

```json:元となるデータ集合
{
  "nickname": "Taka"
}
```

クレーム`nickname`のDisclosureの元となるJSON配列を用意します。

```json:Disclosureの元となるJSON配列
["oR5tdCF7VwDus686vmMJhg","nickname","Taka"]
```

このJSON配列をUTF-8のバイト配列に変換してからbase64urlでエンコードし、Disclosureを作ります。

```:Disclosure
WyJvUjV0ZENGN1Z3RHVzNjg2dm1NSmhnIiwibmlja25hbWUiLCJUYWthIl0
```

このDisclosureのダイジェストを求めます。

```:Disclosureのダイジェスト
LKDKhh6QphYDwMmYk9yCYF_5147DiD7QJ1KD0j2lxFI
```

このダイジェストを含む`_sd`配列を持つJSONオブジェクトを用意します。

```json:Credential JWTのペイロード
{"_sd":["LKDKhh6QphYDwMmYk9yCYF_5147DiD7QJ1KD0j2lxFI"]}
```

上記のJSONオブジェクトをペイロードとして持つJWTを生成します。

```jwt:Credential JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiTEtES2hoNlFwaFlEd01tWWs5eUNZRl81MTQ3RGlEN1FKMUtEMGoybHhGSSJdfQ.aMLcoFybXNO9cSlGWCfarLS0eSMig4jSK57VCwATcmORgIwJsceIthPy2kz0UNDcI0xer2-tYPBAtfdybDhoWQ
```

このJWTの後ろに、チルダ(`~`)と先ほどのDisclosureを繋げ、さらに末尾にもう一つチルダを置き、文字列を生成します。

```:SD-JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiTEtES2hoNlFwaFlEd01tWWs5eUNZRl81MTQ3RGlEN1FKMUtEMGoybHhGSSJdfQ.aMLcoFybXNO9cSlGWCfarLS0eSMig4jSK57VCwATcmORgIwJsceIthPy2kz0UNDcI0xer2-tYPBAtfdybDhoWQ~WyJvUjV0ZENGN1Z3RHVzNjg2dm1NSmhnIiwibmlja25hbWUiLCJUYWthIl0~
```

見やすく改行すると次のようになります。

```:SD-JWT (見やすく改行)
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.
eyJfc2QiOlsiTEtES2hoNlFwaFlEd01tWWs5eUNZRl81MTQ3RGlEN1FKMUtEMGoybHhGSSJdfQ.
aMLcoFybXNO9cSlGWCfarLS0eSMig4jSK57VCwATcmORgIwJsceIthPy2kz0UNDcI0xer2-tYPBAtfdybDhoWQ
~
WyJvUjV0ZENGN1Z3RHVzNjg2dm1NSmhnIiwibmlja25hbWUiLCJUYWthIl0
~
```

:::note info
[RFC 3986 Uniform Resource Identifier (URI): Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986.html)の[Section 2.3. Unreserved Characters](https://www.rfc-editor.org/rfc/rfc3986.html#section-2.3)は、英数字、`-`、`_`、`.`、`~`を非予約文字としています。`~`以外の文字はJWTで利用されるので、JWTを混在させた文字列を構築したいときにパーセントエンコーディング不要で使える区切り文字として残っていたのは`~`のみでした。
:::

## プレゼンテーション時のフォーマット

HolderからVerifierにVPを渡す際、SD-JWTを利用する方法では、0個以上の選択的開示可能な要素をペイロード部に含むJWTと、開示したいデータに対応するDisclosure群を渡します。また、任意でHolder Binding JWT (HB-JWT) (後述) も渡します。

```plantuml
left to right direction
agent Holder
agent Verifier
Holder-->Verifier : Credential-JWT + Disclosure群 (+ HB-JWT)
```

発行時のフォーマットと同様、これらの要素をチルダ(`~`)で連結します。HB-JWTを付ける場合は末尾にチルダを付けない点は注意が必要です。

```html:SD-JWT (HB-JWT有り)
<Credential-JWT>~<Disclosure1>~...~<DisclosureN>~<HB-JWT>
```

```html:SD-JWT (HB-JWT無し)
<Credential-JWT>~<Disclosure1>~...~<DisclosureN>~
```

## Holder Binding JWT

VerifierがHolderからVPを受け取ったとき、そのHolderがSD-JWTの正統な保持者であることを確認するにはどうすればよいでしょうか？別の言い方をすると、SD-JWTがそのHolderに対して発行されたものであることを確認するにはどうすればよいでしょうか？

この確認作業を可能とするために用いられるのが**Holder Binding JWT** (以降HB-JWT) です。

HB-JWTの基本的なアイディアは次の通りです。

:one: VCの発行を依頼する際、Holderは公開鍵をIssuerに渡す。
:two: Issuerは、その公開鍵を他のデータと一緒にデータ集合に含め、VCを発行する。
:three: Holderは、その公開鍵に対応する秘密鍵で特定のデータに署名をおこなう。
:four: HolderがVPをVerifierに渡す際は、上記手順で用意した署名されたデータも一緒に渡す。
:five: Verifierはデータ集合から公開鍵を取り出し、署名されたデータの署名を検証する。
:six: 検証が通れば、当データ集合がIssuerからHolderに発行されたものであることが確認できる。

```plantuml
Holder -> Issuer : VC発行リクエスト + Holderの公開鍵
Issuer -> Holder : Holderの公開鍵を含むVCを発行
Holder -> Verifier : VP (Holderの公開鍵、Holderの秘密鍵で署名されたデータ、を含む)
Verifier -> Verifier : Holderの公開鍵を用いて、署名を検証する。
```

VCに公開鍵を含める方法は幾通りも考案することはできますが、SD-JWT仕様では`cnf`クレーム ([RFC 7800](https://www.rfc-editor.org/rfc/rfc7800.html) [Section 3.1. Confirmation Claim](https://www.rfc-editor.org/rfc/rfc7800.html#section-3.1)) を用いる方法を例示しています。

```json:Holderの公開鍵を含むSD-JWTペイロードの例
{
  "_sd": [
    "5nXy0Z3QiEba1V1lJzeKhAOGQXFlKLIWCLlhf_O-cmo",
    "9gZhHAhV7LZnOFZq_q7Fh8rzdqrrNM-hRWsVOlW3nuw",
    "S-JPBSkvqliFv1__thuXt3IzX5B_ZXm4W2qs4BoNFrA",
    "bviw7pWAkbzI078ZNVa_eMZvk0tdPa5w2o9R3Zycjo4",
    "o-LBCDrFF6tC9ew1vAlUmw6Y30CHZF5jOUFhpx5mogI",
    "pzkHIM9sv7oZH6YKDsRqNgFGLpEKIj3c5G6UKaTsAjQ",
    "rnAzCT6DTy4TsX9QCDv2wwAE4Ze20uRigtVNQkA52X0"
  ],
  "iss": "https://example.com/issuer",
  "iat": 1516239022,
  "exp": 1735689661,
  "_sd_alg": "sha-256",
  "cnf": {
    "jwk": {
      "kty": "EC",
      "crv": "P-256",
      "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc",
      "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"
    }
  }
}
```

# SD-JWT実装

[oauth-wg](https://github.com/oauth-wg)/[oauth-selective-disclosure-jwt](https://github.com/oauth-wg/oauth-selective-disclosure-jwt)リポジトリのREADMEの[Implementations](https://github.com/oauth-wg/oauth-selective-disclosure-jwt#implementations)セクションにSD-JWTの実装がリストされています。

ただし、SD-JWT仕様がIETFのドラフトになる前 ([draft-fett-oauth-selective-disclosure-jwt](https://datatracker.ietf.org/doc/draft-fett-oauth-selective-disclosure-jwt/)) と後 ([draft-ietf-oauth-selective-disclosure-jwt](https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/)) では仕様がかなり異なります。 例えば、`sd_digests`は無くなっており、`sd_digests`の代替となる`_sd`はあるものの、フォーマットがかなり異なるので、もはや別物です (リネームすれば使えるという類のものではありません)。ですので、昔の仕様に基づいて書かれたSD-JWTの実装は仕様の最新版に準拠する形式でデータを作成できないので注意してください。

## Javaによる実装

OpenID for Verifiable Credential Issuance (OID4VCI) などのVerifiable Credentialsに関連する仕様をAuthleteが今後実装する際に必要となるので、SD-JWTのJava実装を書きました。企業秘密にするほどのものでもないので、[authlete](https://github.com/authlete)/[sd-jwt](https://github.com/authlete/sd-jwt)にてオープンソースとして公開してあります。使い方の詳細はREADMEファイルをご覧ください。

### Credential JWTを作成する例

```java:CredentialJwtGenerationExample.java
import java.util.*;
import com.authlete.sd.*;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;
import com.nimbusds.jwt.*;

public class CredentialJwtGenerationExample
{
    public static void main(String[] args) throws Exception
    {
        //------------------------------------------------------------
        // SD-JWTライブラリを使用する部分
        //------------------------------------------------------------

        // Credential JWTのペイロード部分を用意するため、SDObjectBuilderの
        // インスタンスを作成する。ここではデフォルトコンストラクタを用いているので、
        // Disclosureのダイジェストを計算する際のアルゴリズムにはデフォルトの
        // sha-256が使用される。ちなみに、SDObjectEncoderというクラスを用いて
        // Credential JWTのペイロード部を用意する方法もある。
        SDObjectBuilder builder = new SDObjectBuilder();

        // クレーム "given_name":"Takahiko" を選択的開示の対象とするため、対応する
        // Disclosureを作成する。この例ではソルトを引数に取らないコンストラクタを用いて
        // いるので、ソルトの値は自動的に生成される。
        Disclosure disclosure = new Disclosure("given_name", "Takahiko");

        // Disclosureのダイジェストを挿入する。この処理により_sd配列が生成される。
        builder.putSDClaim(disclosure);

        // 選択的開示の対象としない (常に開示する) クレームの例として、
        // "family_name":"Kawasaki" を追加する。
        builder.putClaim("family_name", "Kawasaki");

        // Credential JWTのペイロード部を表すMapインスタンスを作成する。claimsには、
        // _sdとfamily_nameがキーとして含まれるほか、build()メソッドにtrueを渡して
        // いるので_sd_algも含まれることになる。
        Map<String, Object> claims = builder.build(true);

        //------------------------------------------------------------
        // 任意のJWTライブラリを使う部分
        //
        // SD-JWTライブラリは意図的にJWT関連の処理に関わらないようにしているので
        // 任意のJWTライブラリと組み合わせて使うことができる。この例では Nimbus
        // JOSE+JWTライブラリを利用している。
        //------------------------------------------------------------

        // Credential JWTのヘッダを用意する。下記の例では
        //
        //     {"alg":"ES256","typ":"vc+sd-jwt"}
        //
        // という内容を持つヘッダを生成している。
        JWSHeader header =
            new JWSHeader.Builder(JWSAlgorithm.ES256)
                .type(new JOSEObjectType("vc+sd-jwt")).build();

        // Credential JWTのペイロードを用意する。
        //
        // ここでは先ほど生成したMapインスタンスをJWTClaimsSetのインスタンスに
        // 変換しているだけ。JWTClaimsSetインスタンスは、SignedJWTクラスの
        // コンストラクタに渡される。
        JWTClaimsSet claimsSet = JWTClaimsSet.parse(claims);

        // Credential JWTを作成する。(署名前)
        SignedJWT jwt = new SignedJWT(header, claimsSet);

        // Credential JWTに署名するための秘密鍵を生成する。
        ECKey privateKey = new ECKeyGenerator(Curve.P_256).generate();

        // 生成した秘密鍵を用いて署名をおこなう署名器を作成する。
        JWSSigner signer = new ECDSASigner(privateKey);

        // Credential JWTに署名する。
        jwt.sign(signer);

        // Credential JWTをシリアライズする。
        // (JWS Compact Serialization形式の文字列を生成する)
        String ser = jwt.serialize();

        // JWS Compact Serialization形式のCredential JWTを出力する。
        System.out.println(ser);
    }
}
```

上記のコードを実行すると、次のような出力が得られます。

```:Credential JWT
eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiVzVEX3dSam9qbUZvYUNpeFVLNVJXSFRsVE1TYnNqZHhHU3daYTltXzZ1RSJdfQ.IiJyysrsFqjyv2n9gmjQehvmxYY5g2-nR_mgtn1BeHkiWTYuK_UEoLn00nSteX1cFj1n8waEYwVO-ytnnTzbKg
```

Credential JWTのペイロード部をデコードすると、次のようになります。見やすくなるように改行を入れてあります。

```json
{
  "_sd": [
    "dizbNUcbL4uEUUrE89MSoD6KAJ1Hn2L1leTpTuUMsX8"
  ],
  "family_name": "Kawasaki",
  "_sd_alg": "sha-256"
}
```

Disclosureをデコードすると次の内容が得られます。

```json
["yxiMbdoRdFG9WiQyOkulFA","given_name","Takahiko"]
```

### SD-JWTを作成する例

```java
// Credential JWT
String credentialJwt = ...;

// Disclosureのリスト
List<Disclosure> disclosures = ...;

// SD-JWTを作成する (HB-JWT無し)
SDJWT sdJwt = new SDJWT(credentialJwt, disclosures);

// SD-JWTを出力する。toString()メソッドは仕様に準拠する文字列表現を返す。
System.out.println(sdJwt);
```

# おわりに

EIC 2023に参加した方々によると、業界は『ウォレット』の話でもちきりだったようです。ウォレットといっても、仮想通貨用のウォレットのことではなくて、Verifiable Credentials (検証可能な資格証明) 用のウォレットのことです (両方ともウォレットと呼ぶので紛らわしい)。

|![EIC2023_Authlete_Booth_Daniel_Henrik_Nat.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/b83d579d-37a5-71f3-fa79-3a9476400f5d.jpeg)|
|:--|
|EIC 2023のAuthlete社ブースの[ダニエル](https://www.linkedin.com/in/dr-fett/)、[ヘンリック](https://www.linkedin.com/in/henrikfalck/) (Authlete社VP of Engineering)、[﨑村さん](https://www.linkedin.com/in/natsakimura/) ([OpenID Foundation](https://openid.net/) Chairman/Authlete社社外取締役)|

ヨーロッパの規制当局にとっては、ウォレットに保存したVerifiable Credentialsの内容を外部に提示する際、必要最小限の情報開示となることがとても重要です。つまり、選択的開示 (Selective Disclosure) が可能かどうかがとても重要です。そのため、選択的開示を実現するための技術であるSD-JWTに注目が集まっています。

|![Selective_Disclosure_by_Daniel_Kristina.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106044/32d03ea9-e8e0-b941-be8d-3f0107d1a832.jpeg)|
|:--|
|SD-JWTのTシャツで選択的開示を表現するダニエルとクリスチーナ。2023年3月に横浜で開催されたIETF 116 Yokohama期間中の業界関係者の夕食会にて。 (クリスチーナの[ツイート](https://twitter.com/kristinayasuda/status/1641042310329954304)にも写真が載ってます)|