はじめに
GMOグローバルサイン・ホールディングスCTO室でデジタルアイデンティティーの研究開発をしている神沼(@t_kanuma)です。
この記事は、SSI/VCモデルとHyperledger Indy/Ariesを使ったシステム構築実証[前編]の後編です。
後編では、私が行った小さなPoC、テーマ「年齢認証」でHyperledger Indy、Ariesを使い、実際にシステムを構築・開発できるか?について、アプリケーションのデモ動画を含めご説明します。
PoC
目的
前述の通り、Hyperledger Indy、Ariesを使い、実際にシステムを構築・開発できるか?を実証します。
テーマ
年齢認証をテーマにします。
コンビニ、スーパー、酒屋などのレジに置いて、お酒を購入する顧客が20才以上であることを認証します。
3つの登場人物は以下の通りです。
- アリス(Holder)
- スーパーマーケットでお酒を買う顧客です。
- Agentの形態はスマホアプリです。
- GMO GlobalSign Holdings、以下GSHD(Issuer)
- アリスの買い物より前に、アリスからのWebフォーム申請を審査して年齢を含んだCredentialを発行します。
- Agentの形態はWebアプリです。
- ボブマーケットと店員ボブ(Verifier)
- アリスからのProofの提示を受け、20才以上であるか、そしてProofが正当なものか検証を行うスーパーマーケットです。
- Agentの形態はWebアプリです。
- ボブマーケットのレジには、アリスとのやりとりを始めるためのQRコードが置いてあります。
Daniel H Hardman,CC BY-SA 4.0,link
フロー(エージェント間のやり取りの流れ)
3部構成です。
フェーズ1: Issuer/Holder間の招待と接続
- アリスはGSHDのWebフォームで所定の情報を入力、送信する。
- GSHDはフォーム情報の真正性を審査してOKであれば、招待コードをアリスにメールで送信する。
- アリスはスマホアプリを開いて招待コードを入力し、GSHDに送信する。
- GSHDは招待コードを受理して、Credential発行のオファーをアリスに出す。
ポイント
- 1及び2は、VCモデルの外で行われます。今回はメール認証を使っています。
- 4からVCモデルの中に入ります。ここからDIDComm Protocolによる通信が開始されます。
フェーズ2: IssuerからHolderへのCredential発行
- オファーを受けたアリスは、スマホアプリからGSHDに対しCredential発行の要求をする。
- アリスの要求に対し、GSHDはWebアプリからCredentialを発行する。
- アリスはスマホアプリで発行されたCredentialを確認する。
ポイント
- 発行の要求、及び発行は、ユーザ操作がトリガーとなります。
- これは私がそのように設計したためです。
- 後述の”Proof提示・検証フェーズ”とは、対照的になっています。
フェーズ3: Holder/Verifier間の接続とProofの提示・検証
- ボブはWebアプリ(ブラウザ)上に接続のためのQRコードを表示する。
- アリスはブラウザ上のQRコードをスマホアプリで読み取る。
- 読み取ったと同時にバックグラウンドで以下が行われる。
- アリスAgentは読み取った招待コードからボブAgentと接続する。
- ボブAgentはアリスAgentと接続後、Proofの要求をする。
- アリスAgentは、CredentialからProofを作成し、ボブAgentに送信する。
- ボブAgentは、Proofの提示を受け検証を行なう。
- 読み取ったと同時にバックグラウンドで以下が行われる。
- ボブは、Webアプリ上で検証結果を確認する。
ポイント
- Proofの要求及び検証は、ユーザ視点ではバックグラウンドで一気に行われます。
- これは私がそのように設計したためです。
- 前述の”Credential発行フェーズ”とは、対照的になっています。
備考
以下は全て設計次第です
- 上記1.招待・接続フェーズのプロトコルは、Aries RFC 0160: Connection Protocolに則っています。今回はIssuer/Verifierから開始しましたがHolderから開始することもできます。
- 上記2.Credential発行フェーズのプロトコルは、Aries RFC 0036: Issue Credential Protocol 1.0に則っています。今回はIssuerのofferから開始しましたが、Proposalという形でHolderから開始することもできます。
- 上記3.Proof提示・検証フェーズのプロトコルは、Aries RFC 0037: Present Proof Protocol 1.0に則っています。今回はVerifierのRequestから開始しましたが、Proposalという形でHolderから開始することもできます。
PoCシステム構成
システム構成概略図
概要
- 主にAWS上でシステムを構築しました。
- 緑がHolder、青がIssuer、赤がVerifierのコンポーネントです。
- 大きな丸が1つのEC2インスタンスです。その中にある7つの四角はそれぞれがDockerコンテナになります。
- 内4つは、Indyネットワークを構成するノードです。
- 内3つは、各AgentのAriesフレームワーク(aca-py)です。
Indyネットワーク
4つのノードそれぞれがDockerコンテナとしてパッケージングされているvon-networkを利用しました。コマンド1つで簡単にネットワークを構築できます。Dockerに加えてDocker Compose環境が必要となります。
aca-py
- aca-pyはその名の通り、Pythonプロセスです。
- aca-py間の点線は、DIDComm Protocolによる通信を意味します。
- aca-pyは、SQLiteを内蔵しており、そこにDIDの秘密鍵そしてHolderに関してはCredentialを保管しています。
- aca-pyで用意されているスクリプトでDockerコンテナをデプロイします。ここでは、起動パラメータの精査と設定が大きな仕事にになります。以下にパラメーターをいくつか例示します。
- アクセスするIndyネットワークのURL
- 自身のDIDComm ProtocolによるEndpointのURL、及びイベントハンドラー(Webhook)のURL
- Walletのストレージタイプ(メモリ、SQLite、PostgreSQL)
アプリケーション/イベントハンドラー
- 各AgentのイベントハンドラーにはAPI GatewayとLambdaを利用し、Pythonで実装しています。
- 前編で述べた通り、aca-pyではアプリの言語、実行環境を開発側が自由に決められます。
アプリケーション/UI周り
- Issuer、VerifierのWebアプリはNext.jsで実装しています。
- フロントエンドのホスティング手段としてAmplifyを利用しています。
- 主にaca-pyの状態とUIの状態を同期させる目的で、リアルタイムでアプリと同期できるGoogle FirebaseのCloud Firestoreを利用しています。
- HolderのスマホアプリはReact Nativeで実装しています。
- 今回は技術的、スケジュール的な都合上、Holderのスマホアプリにaca-pyを利用しました。しかし、aca-pyは本来クラウド側専用のフレームワークです。このやり方ではCredentialと秘密鍵はサーバ側に保管され、現実的には個人ではなくサービスプロバイダーが管理する形になってしまいます。これはSSIの考え方に合っていません。Credentialと秘密鍵はスマートフォンに保管されるのが基本形であるため、aries-framework-dotnetやaries-framework-javascriptなどモバイルアプリを開発可能なフレームワークを使うのが本来の姿だと考えます。
- 前編で述べた通り、aca-pyではアプリの言語、実行環境を開発側が自由に決められます。
その他
- Holder-Issuer間のVCモデル外での招待コードのメール認証にLambdaを利用し、Node.jsで実装しています。
- aca-pyのAPI EndpointはHTTPSでなくHTTPです。WebアプリからのMixed Contentを回避するため、HTTPS化をするリバースプロキシとしてCloudFrontを利用しています。
フローと照らし合わせたポイント
- イベントハンドラーがイベントを受け取った際の処理は以下の2つに大別できます。
- Cloud Firestoreに状態を書き込み、次のプロトコル推進をユーザに任せる。
- イベントを受けとったハンドラー自身が、すぐさまaca-pyの所定のAPI Endpointを呼び出し、プロトコルを進める。
- Holder-Issuer間はイベントをUIまで返しユーザに操作を委ねる場合と、イベントハンドラー間で、すなわちユーザから見るとバックグラウンドでプロトコルを進める場合があります。
- Holder-Verifier間は、接続から検証までをイベントハンドラー間で行います。ユーザから見るとバックグラウンドで一気に行われます。
DIDを中心とした情報
デモの前に、フローを始める前とその最中におけるDIDを中心とした情報を見ていきます。
Issuerの準備:Indy上の4種類のTransaction
フローを始める前に、GSHD(Issuer)は自身が発行するCredentialに対応する4種類のTransactionをIndy上に作成しておく必要があります。
- TYPE:NYM
- NYMはpseudonym(作家のペンネーム、筆名)の略だと推測します。
- NYMの値がGSHDのPublic DIDをBase58でエンコードしたものです。
- Role:ENDORSERは、IssuerのIndy上でのRoleです。
- VerkeyはこのDIDに紐づく公開鍵です。値はBase58でエンコードされたものです。
- 詳細は省きますが、このレコードはIndyの別ロール、Trustee(このネットワークの創始者、運営者)またはSteward(ネットワークに参加しているトランザクション検証ノード)により作成されます。
- TYPE:ATTRIB
- GSHD(Issuer)のDIDComm ProtocolによるEndpoint情報を持ちます。
- このレコードはaca-pyが自身の起動時に作成します。詳細は省きますが、起動パラメータにseedを指定します。これは擬似乱数のseedのようなもので、Trustee/Stewardが作成するNYMレコード、すなわちDIDとその公開鍵ペアの素になるものだと推測します。そうすると、IssuerはTrustee/Stewardからこのseedをセキュアな経路で受け取る必要が出てきます。このseedをパラメータに設定することで、IssuerはIndy上のDIDを所有することが認められるのだと思います。
- TYPE:SCHEMA
- Credentialのスキーマ定義です。
- このPoCではnameとageという2つのClaimを持っていることがわかります。
- このレコードは、NYM/ATTRIBレコードを作成後、aca-py API経由でGSHD(Issuer)が作成します。
- TYPE:CRED_DEF
フロー内でのDID関連情報
ここでは、このPoCの各フェーズにおいて、アプリがaca-py APIから取得できる実際の値をサンプルとして5つ列挙します。
- Agent間で接続を確立した後のConnection情報
- この接続における自身と相手のPairwise DID、接続ID、状態などを見れます。
{
"request_id": "b8a1d47a-20e6-414d-87ae-d1e541974efa",
"invitation_mode": "once",
"initiator": "external",
"routing_state": "none",
"my_did": "AbejbTQ86GPKM9PVg3KrP3",
"created_at": "2020-09-24 05:32:24.609925Z",
"their_did": "NPBWhzn5MTic5W6YCKwmUy",
"their_label": "agify-isser-acapy",
"connection_id": "cf185942-cb4a-4567-adf3-1752ec29f091",
"state": "active",
"updated_at": "2020-09-24 05:32:25.826680Z",
"invitation_key": "9gkERW9PQ2URngCEXrDF2LM1tabD1kj9fg9GWSs4z7or",
"accept": "auto"
}
- 各AgentがWalletに持つDIDの一覧
- 自身のDIDとその公開鍵(verkey)が見て取れます。wallet_onlyはPairwise DIDであることを意味していると考えます。
{
"did": "2Bzip889RLZ57kxe2NZnrP",
"verkey": "ec5HdZddLJAvj82qKLN1HKYiiMjKaynMroDTqkL39v3",
"posture": "wallet_only"
},
{
"did": "2Hmzbu7pKSWCQoxbyziL6p",
"verkey": "hkouN5JpSQxYKLMzggq8Sy8ADivrLEzJxxbqUjuYs63",
"posture": "wallet_only"
}
- HolderがWalletに持つCredential
- attrsからCredentialの各Claimの値が見てとれます。
- schema_id及びcred_def_idから、Indy上のどのSchema、Credential Definitonに基づいたモノであるかがわかります。
{
"referent": "credential_sample",
"attrs": {
"age": "34",
"name": "Taro Tanaka"
},
"schema_id": "Vc7MmqrjX1mj1ertBeA6vP:2:agify:1.0",
"cred_def_id": "Vc7MmqrjX1mj1ertBeA6vP:3:CL:87:default",
"rev_reg_id": null,
"cred_rev_id": null
}
- IssuerがHolderに送信するCredential発行オファーの内容抜粋
- 平たく言うと、”Holderさん、このschema_id、cred_def_idのCredential発行しません?”ということです。
{
"auto_offer": false,
"schema_id": "Vc7MmqrjX1mj1ertBeA6vP:2:agify:1.0",
"comment": "Offer on cred def id Vc7MmqrjX1mj1ertBeA6vP:3:CL:87:default"
},
"trace": false,
"credential_exchange_id": "6fc6ec52-a21f-4f27-a600-683ba4bd6c5d",
"initiator": "self",
"auto_remove": false,
"connection_id": "9b324014-5807-49b8-b680-dc76d5e6786c",
"credential_offer": {
"schema_id": "Vc7MmqrjX1mj1ertBeA6vP:2:agify:1.0",
"cred_def_id": "Vc7MmqrjX1mj1ertBeA6vP:3:CL:87:default",
},
"state": "offer_sent",
}
- VerifierがHolderに送信するProof要求の内容抜粋
- 平たく言うと、”Holderさん、このIssuerが署名したnameとageデータでProofを作ってね。ゼロ知識証明の条件は、age>=20だよ。”ということです。
- ここで重要な点は、ProofがDIDと疎結合になっていることです。
- これにより複数のIssuerが発行した複数のCredentialから必要なClaimを組み合わせてProofを作れます。
- Issuerが各Claimごとに署名しているのはこのためです。
{
"state": "request_sent",
"presentation_request": {
"name": "age schema",
"requested_attributes": {
"0_name_uuid": {
"name": "name",
"restrictions": [
{
"issuer_did": "Vc7MmqrjX1mj1ertBeA6vP"
}
]
}
},
"requested_predicates": {
"0_age_GE_uuid": {
"name": "age",
"p_type": ">=",
"p_value": 20,
"restrictions": [
{
"issuer_did": "Vc7MmqrjX1mj1ertBeA6vP"
}
]
}
}
デモ
以下の動画をご確認ください。流れはフローと照らし合わせてご覧ください。
「フェーズ1: 招待・接続」 & 「フェーズ2: Credential発行」
- Holder
- Webフォームの内容について、実運用を仮定すると運転免許証のコピーなどが必要になると思われますが、このPoCではこの通りです。
- ”クレデンシャル”ボタンから発行されたCredentialを確認できます。
- ”履歴”ボタンは、過去のProof提示履歴を表示します。
- 画面右上のボタンはIssuer及びVerifierからの通知を表示します。
- Issuer
- メニュー”申請一覧”は、このアプリのメインの画面です。ここにHolderからのフォーム申請、Credential発行要求がリアルタイムで表示、更新されます。
- メニュー”接続一覧”は、過去に行なった接続とその状態の情報を表示します。
- メニュー”DIDDocs”は、自身のIndy上のCredentialのスキーマ情報を表示します。
フェーズ3: Proof提示・検証
- Holder
- VerifierのQRコードを読み取るのみのため、動画は割愛します。
- Verifier
- メニュー”検証”は、更新するたびに変わるQRコードを表示します。検証結果を画面に緑丸(成功)か赤丸(失敗)でリアルタイムで表示します。
- メニュー”履歴”は、過去の検証の結果を表示します。
- メニュー”DIDDocs”は、IssuerのIndy上のCredentialのスキーマ情報を表示します。
総括
実証結果
フローを完遂し、年齢が20才以上であればVerifierの検証が成功し、20才未満であれば検証が失敗することを確認できました。以上から、テーマ「年齢認証」でHyperledger Indy、Ariesを使い、実際にシステムを構築・開発できることを実証できました。
考察
開発の容易性について
前編で述べた通り、以下のことはAriesフレームワークが隠蔽してくれます。
- DIDの生成とAgent間のDIDComm Protocolでのセキュアな通信
- Indyへのアクセス(情報格納・取得)
- Credentialの発行、署名、保存
- Proofの提示、検証(ゼロ知識証明含む)
- Walletの管理
- アプリへのイベント通知、アプリからのリクエスト受付
そのため、Webの知識・技術があればフレームワークが提供する高レベルなAPIを使うことでSSIアプリケーション・システムを構築できます。これはSSIを広く浸透させる、スケールさせるための作りだと思います。
Indyについては、自前でブロックチェーンネットワークを始めたり、既存ネットワークに参加してトランザクション検証ノードを運用する場合は、根本から理解が必要になると思われます。
実運用を仮定した場合のシステムの差異について
- Indyの環境
- 今回の環境はあくまでも開発用のネットワークです。 実運用では、ノードごとに別々の企業・団体によって厳格なガバナンスの元、管理・運用されるモノです。
- SSIによるソリューションが目的であれば、現実的には前述のSovrinのような既存環境を使うことになると思います。
- WalletのDB
- aca-pyは軽量なRDBであるSQLiteを内蔵しており今回はそれを使いましたが、Postgresqlを使うことも出来ます。
- 本番運用の信頼性を考えるとPostgresql化するのが望ましいと考えます。
- Holderアプリのフレームワーク
- 繰り返しになりますが、aca-pyではなくaries-framework-javascriptやaries-framework-dotnetを使う事になると考えます。
- 追記:ご興味をお持ちいただけましたら、この記事をご参照ください。【Hyperledger Aries】モバイルアプリ型Holder Agentの開発における選択肢
- ルーティングエージェントの用意
- インターネットの仕組み上、モバイルエージェントはそれだけではP2Pで他エージェントと通信できるわけではありません。ルーティング処理専門の別エージェントをインターネット上に用意する必要があります。これはGoogleのGCM/FCM、AppleのAPNsのような存在で、個人ではなく、例えばスマホアプリを提供するサービスプロバイダなどが用意する事になると考えます。
- 追記:ご興味をお持ちいただけましたら、この記事をご参照ください。【Hyperledger Aries】Aries Cloud Agent – PythonにおけるMediation機能を試す
- aca-pyコンテナのクラスタ化
- 今回は各aca-pyのコンテナは1つでしたが、スケーラビリティとアベイラビリティーを考えて、KubernetesやAWS ECSなどでクラスタ化することになると思います。
- 追記:ご興味をお持ちいただけましたら、この記事をご参照ください。ECS on FargateでHyperledger Ariesをコンテナオーケストレートする
最後に
以上でこの記事を終わります。
最後までお読みいただき、ありがとうございました。
参考サイト
- edX
- Introduction to Hyperledger Sovereign Identity Blockchain Solutions:Indy,Aries&Ursa
- Becoming a Hyperledger Aries Developer
謝辞
今回のPoCを私と共同開発してくれた(JavaScript周りの全部を開発してくれた)yushimatenjinこと弊社グローバルサービスプロバイダー部の羽賀にBig Thanksを送ります。