ドーモ。最近はずっとDID/VCを追っている私です。
本投稿では、クレデンシャル要求・クレデンシャル提示の標準仕様として策定されている DIF Presentation Exchange について解説します。(最近触る機会があり、業務で実際に実装を行ったので、そのメモも兼ねています。)
なお、DIDやVCについてはもう皆さんご存知だと思いこんで解説を省略します。ご了承ください。
概要
下の図はVCの解説でよく出てくる3者図です。
Issuer(発行者)が発行したVCをHolder(保持者)が保管し、必要に応じてVerifier(検証者)にVerifiable Presentation(VP)として提示する、というのがVCの基本的な使われ方です。
Presentation Exchangeは、この図のうちHolder→Verifierの部分、つまりVPの要求と提示に関する標準フォーマットを定める仕様となっています。
Presentation Exchange仕様では、以下の2つの文書フォーマットを定めています。
- 要求するVPの要件を定義するpresentation definition
- 提示するVPが必要な要件を満たしていることを説明するpresentation submission
この2つの文書はJSON形式で定義されており、ウォレットアプリやサーバプログラムの中で形式的に解釈することができます。また、VPの要件の定義にはJSON Path/JSON Schemaのようなよく知られた仕様が用いられており、既存のライブラリを使ってVPの検証ロジックを組み立てることが可能になっています。
サンプルを見てみる
細かい仕様の説明は仕様書に任せるとして、実際どのようなドキュメントをやりとりするのかを例示してみます。
今回は次のようなユースケースを想定してみます。
- お酒を販売する通販サイトで、DIDユーザーを受け入れている。
- 注文の受付の際、ユーザーの年齢確認ができるVCを提示してもらう必要がある。
ミニマムな例
一番単純に考える場合、presentation definitionは例えば下記のように書けます。
{
"id": "<definition自体の識別用ID>",
"input_descriptors": [
{
"id": "some_credential",
"name": "年齢確認書類",
"purpose": "年齢確認のために使用します",
"constraints": {
"fields": [
{
"purpose": "年齢確認のために使用します",
"path": [
"$.credentialSubject.dateOfBirth",
"$.vc.credentialSubject.dateOfBirth",
]
"filter": {
"type": "string",
"format": "date",
"formatMaximum": "2002-12-08"
}
}
]
}
}
]
}
トップレベルにある"input_descriptors"属性は配列で、中に入っているオブジェクト(InputDescriptorObject)がそれぞれ1つのVCに関する要件を記述しています。
今回の例では「年齢確認書類」という題目で、以下のような条件のVCを要求しています。
- 指定のpath(のいずれか)に"dateOfBirth"という属性が含まれていて、
- その値は
string
型かつISO形式の日付データで、 - しかも
"2002-12-08"
以前の日付であるもの。
要するに「誕生日から20歳以上であることがわかるようなVC」ということですね。
上記のpresentation definitionに対して、次のようなVPを返すとします。
{
"@context": [...],
"holder": "<保持者DID>"
"verifiableCredential": [
{
"@context": [...],
"issuer": "<発行者DID>",
"credentialSubject": {
"id": "<保持者DID>",
"dateOfBirth": "2000-01-01"
},
"proof": {...}
}
],
"proof": {...}
}
このVPでは、$.verifiableCredential[0]
のオブジェクトが要求されている要件を満たしています。
そのことを説明するために、HolderはVPと一緒に、次のようなpresentation submissionを送ることになります。
{
"id": "<presentation submission自体の識別用ID>",
"definition_id": "<対応するpresentation definitionの識別用ID>",
"descriptor_map": [
{
"id": "some_credential",
"format": "ldp_vc",
"path": "$.verifiableCredential[0]"
}
]
}
年齢確認書類(id="some_credential")はVPの"$.verifiableCredential[0]"に含まれていて、そのフォーマットはJSON-LD形式のVCだよ、ということを説明する文書になっています。descriptor_map属性が、definition中のinput descriptorとVP上のJSON Pathのマッピングを提供するというイメージですね。端的でわかりやすく、プログラム的にも処理しやすいと思います。
このあとVerifierはVPとpresentation submissionを受け取ったあと、自分が送ったpresentation definitionと突き合わせて条件を満たしているかを確認するのですが、その処理ロジックも基本的にはstraight-forwardで書くことができます(できました)。
より複雑な例
さて、上記の例では誕生日属性以外の条件をつけていませんでした。しかし、実際のサービスで年齢確認をするのであれば、もっと厳しい要件が必要になるかと思います。例えば……
- 提示できる書類の種類。適当に作れるようなVCではなく、より信頼性の高い形式のもの
- VCの発行者の指定。Holderの自称ではなく、信頼できるIssuerを指定するなど
- holderとsubjectが同一であることの保証。当然、他人のVCを提示されても困る
これらの条件をpresentation definitionで書き下してみると、下記のようにどんどん複雑で長ったらしくなります。
{
"id": "32f54163-7166-48f1-93d8-ff217bdb0653",
"submission_requirements": [ // 組み合わせ条件。「グループAのVCのうちどれか一つを提示せよ」
{
"name": "年齢確認書類",
"purpose"; "年齢確認のために使用します",
"rule": "pick",
"count": 1,
"from": "A"
}
],
"input_descriptors": [
{
"id": "driverLicense",
"group": "A",
"name": "運転免許証",
"purpose": "年齢確認のために使用します",
"constraints": {
// 特定のsubの値がholderの値と一致
"is_holder": [{"field_id": "driverLicense_id", "directive": "required"}],
"fields": [
// VCの型指定
{
"path": ["$.type", "$.vc.type"],
"filter": {
"contains": {
"const": "DriverLicenseCredential"
}
}
},
// 特定の発行者のみ許容
{
"path": ["$.issuer", "$.vc.issuer", "$.iss"],
"filter": {
"const": "<DID of known and trusted issuer>"
}
},
{
"id": "driverLicense_id",
"path": ["$.credentialSubject.id", "$.vc.credentialSubject.id", "$.sub"]
},
{
"purpose": "年齢確認のために使用します",
"path": [
"$.credentialSubject.dateOfBirth",
"$.vc.credentialSubject.dateOfBirth",
]
"filter": {
"type": "string",
"format": "date",
"formatMaximum": "2002-12-08"
}
}
]
}
},
{
"id": "healthCard",
"group": "A",
"name": "健康保険証",
"purpose": "年齢確認のために使用します",
"constraints": {
"is_holder": [{"field_id": "healthCard_id", "directive": "required"}],
"fields": [
{
"path": ["$.type", "$.vc.type"],
"filter": {
"contains": {
"const": "HealthCardCredential"
}
}
},
{
"path": ["$.issuer", "$.vc.issuer", "$.iss"],
"filter": {
"const": "<DID of known and trusted issuer>"
}
},
{
"id": "healthCard_id",
"path": ["$.credentialSubject.id", "$.vc.credentialSubject.id", "$.sub"]
},
{
"purpose": "年齢確認のために使用します",
"path": [
"$.credentialSubject.birthDate",
"$.vc.credentialSubject.birthDate",
]
"filter": {
"type": "string",
"format": "date",
"formatMaximum": "2002-12-08"
}
}
]
}
}
]
}
他にもselective-disclosureを要求したり、VCのフォーマットを指定したりなど、presentation definitionで指定できることは結構色々あり、かなり汎用的な作りになっていると感じられます。
使われどころ
あまり調べきれていませんが、Microsoftが主導しているOIDC4SSI / Entra Verified IDにおいては、presentation exchangeがVP要求/提示のプロトコルで採用されています。今後、他の仕様でも採用されることが増えるのではないでしょうか。
ツッコミどころ
path指定はJSON Pathで良いのか?というのは少し気になりました。おそらく実装の容易さや汎用性のためにJSON Pathを採用したのだと思うのですが……。
VCは基本的にJSON-LD形式(=RDF形式)のため、同一のドキュメントを異なる表現形式(compact/expand/flatten/framedなど)に変換することができます。そうすると当然、同じ属性でもJSON上のPathは変わってしまうため、JSON Pathだけだと厳密な属性の指定はできないのでは……と言う懸念があります。
とはいえ、実際のクレデンシャルはある程度形式が固定されていくものと思われますので、それほど気にする必要がないのかもしれません。このあたりは詳しい人にご意見を伺いたいです。
まとめ
Presentation Exchangeを使えば、holderとverifierでVPの要求・提示を形式的に行うことができます。今後この仕様に基づくサービスやwalletアプリが普及すれば、クレデンシャルのデジタル化・自己主権化がより進むのではないでしょうか。
また、definition/submissionの検証は形式的に行えるので、ユーザーが特に意識しなくても適切なVCを適切な相手に提示したり、複雑な手続きを自動で行うなど、様々なユースケースが考えられそうです。