TBDのWeb5 JS SDKとSSI SDKを試す(中編)

こんにちは。GMOグローバルサイン・ホールディングスCTO室で非集中型アイデンティティの研究をしている開発者の神沼@t_kanumaです。

この記事は以下の前編の続編です。

TBDのWeb5 JS SDKとSSI SDKを試す(前編)

参考資料

DIF JapanのMonthly Call(2023/12)にて発表した際のスライドです。
前編の内容を深掘りする内容になっています。

Speaker Deck: Web5の全体像

概要

前編で述べた通り、Web5 JS SDKを使い簡易なDecentralized Web Appを作る試みをします。

筆者自作の何かと色々試すときに使っている以下のメモアプリがあります。
このアプリはフロントエンドにVueを利用し、バックエンドはAWSサーバレス環境でのGraphQL APIという典型的なSPAです。これを後述の環境図に示すブラウザ上で動作するDWAに変更し、2つのDWN間でデータを共有することを試します。

アプリ仕様

ユースケース

仕事上のパートナーであるAliceとBobは、アプリを通し互いにメモを共有します。

機能

アプリはメモに対し基本的なCRUD機能を持ちます。
基本的には、Alice(Bob)が自分のデータを自分のRemote Nodeに置き、許可を与えられたBob(Alice)がそれを読み取るという形です。

  • 作成
    • アプリはまず自Local Nodeにメモを作成し、その後に自Remote Nodeに同期します。
  • 読取
    • アプリは自Local Node上の自分で作ったメモを読み取ります。
    • アプリは相手Remote Node上にアクセスし、許可されたメモを読み取ります。
  • 更新
    • アプリは自Local Node上の既存メモを更新し、自Remote Nodeに同期します。これを行えるのは自分だけです。
  • 削除
    • 更新機能と同様です。

環境

以下、ポイントです。

  • ユーザーはブラウザ上で同じアプリを利用します。
  • Local NodeのストレージはChrome内のIndexed DB(Level DB)であり、オリジンごとに管理されます。
  • Alice
    • Local Nodeのホスト先: localhost(PC上のコンテナ)
    • Remote Nodeの場所: EC2インスタンス上のコンテナ
  • Bob
    • Local Nodeのホスト先: localhost(PC上のコンテナ)
    • Remote Nodeの場所: EC2インスタンス上のコンテナ

構築 / 実装

Remote Nodeの構築

上記環境図の通り、EC2インスタンス内にTBDのDWN Serverのコンテナを1つ建てます。その中にAliceとBobそれぞれのRemote Nodeが作られる形です。(試行であるためローカルのコンテナ上に建てても問題ないと思いますが、実際の運用に近づけた方がわかりやすいと考え、クラウドに建てています。)

以下、docker-compose.ymlです。

services:
  dwn-server:
    image: ghcr.io/tbd54566975/dwn-server:dwn-sdk-0.2.12
    ports:
      - 3000:3000
    volumes:
      - data:/dwn-server/data
    environment:
      - DS_PORT=3000
volumes:
  data:

備考: DIDのDWN Serverへの登録

DWN Serverのデフォルト設定では、全てのDIDに対しRemote Nodeとしての利用を許可します。参考資料 P.29で述べていた”Remote NodeとしてDWN Serverの利用を登録、許可する”の方法についてですが、開発ドキュメントのRegistration Requirementsに、DWN Serverが提供する2つの手法が書かれています。

DWA/Local Nodeの実装

Vue/VuetifyとWeb5 SDKを用いてDWA/Local Nodeを実装します。
細かい点はコードリポジトリをご参照ください。

ポイントを列挙します。

@web5のパッケージ

package.jsonを見てもらえるとわかりますが、参考資料P.14の通り、@web5/apiパッケージをインストールして使います。

ポイントとして、今回はブラウザ上のDWAであるため、このパッケージのブラウザ環境に対応したJSファイルをCDNからインポートして使っています。(2024/3/15追記: @web5/apiのpackage.jsonをよく確認した所、下記に追記したようにyarnインストールしたモジュール内のブラウザ対応JSファイルをimportして動かせました。)

// src/modues/Web5s.ts
import { Web5 } from "https://cdn.jsdelivr.net/npm/@web5/api@0.8.4/dist/browser.mjs";

// 2024/3/15追記: もしくは
// import { Web5 } from "@web5/api/browser";

また、型定義ファイルを含むこのパッケージをdevDependenciesにyarnインストールした上で、CDN経由のJSと型定義ファイルを繋ぐために、以下のモジュール定義を用意しています。

// src/types/cdn.d.ts
declare module "https://cdn.jsdelivr.net/npm/@web5/api@*" {
  export * from "@web5/api";
}

// 2024/3/15追記: もしくは
// declare module "@web5/api/browser" {
//   export * from "@web5/api";
// }

バージョンと依存するパッケージは以下の通りです。

"@web5/api@^0.8.4":
  version "0.8.4"
  resolved "https://registry.yarnpkg.com/@web5/api/-/api-0.8.4.tgz#b78ea5b54b44345764db87b64a8f552e68514468"
  integrity sha512-3kqh7KQeeffsOBi+K5Vhpa59+ZgdI1em16drrRd7h59Jj4m8a09Enhw7WjWNLre7rTG84hQqB9+rpS1VHnOaKQ==
  dependencies:
    "@tbd54566975/dwn-sdk-js" "0.2.10"
    "@web5/agent" "0.2.5"
    "@web5/common" "0.2.2"
    "@web5/crypto" "0.2.2"
    "@web5/dids" "0.2.4"
    "@web5/user-agent" "0.2.5"
    level "8.0.0"
    ms "2.1.3"
    readable-stream "4.4.2"
    readable-web-to-node-stream "3.0.2"

プログラム構造

上図の通りアプリの中でメインとなるのはsrc/modules/memo.tssrc/modules/web5.tsです。
以下、補足です。

  • アプリ起動時に生成したWeb5インスタンスをシングルトンとしてアプリケーションスコープで保持し使い回していますが、Web5インターフェイスを使う際に、毎度Web5.connect(options)を呼び出しても(等値ではないが)等価のインスタンスを取得する模様です。
    • そのため、ブラウザを閉じて再度アプリを開始しても、既存のデータにアクセスできます。 ここの処理については、@web5/user-agentをご参照いただくと理解が深まると思います。
  • Protocol Definitionは、Web5インスタンス生成時に合わせて作っています。

Web5インスタンス生成

以下、Web5インスタンス生成のパラメーターになるOptionの内容です。
DWN EndpointsにEC2上のRemote Nodeの情報を設定しています。

// src/modues/Web5s.ts
const options: Web5ConnectOptions = {
  // LocalからRemoteに同期する間隔(デフォルト: 2分)
  sync: "20s",
  techPreview: {
    // EC2上のDWN Serverのエンドポイントを設定する
    dwnEndpoints: [`${import.meta.env.VITE_REMOTE_DWN_ENDPOINT}`],
  },
};

const { web5, did } = await Web5.connect(options);

Protocol Definition

参考資料P.41でも述べたProtocol Definitionです。

// src/modules/web5.ts
import type { ProtocolDefinition } from "@tbd54566975/dwn-sdk-js";

const protocolDef: ProtocolDefinition = {
  protocol: Web5s.#protocol,
  // 他者がweb5.protocol.query(..)でこのDefinitionをとることが可能になるか否か
  published: false,
  types: {
    memo: {
      schema: "https://gmogshd-cto-office/schema/memo",
    },
  },
  structure: {
    memo: {
      $actions: [
        { who: "author", of: "memo", can: "write" },
        { who: "recipient", of: "memo", can: "read" },
        { who: "author", of: "memo", can: "update" },
        { who: "author", of: "memo", can: "delete" },
      ],
    },
  },
};

ポイントを列挙します。

  • 以下を参考に作りました。
  • 前述した通り、作成、更新、削除できるのは自分だけで、許可された他者が読み取れる、という内容になっています。
  • protocolプロパティ値のURIについては、開発ドキュメントによれば理想的にはこのProtocol DefinitionのJSON Schemaに解決され、バリデートされるべきだが、それはWeb5 JS SDKは対応しないとあります。
  • typesプロパティ内のschemaのURI値も同様で、理想的にはそのTypeのJSON Schemaを返すべきだが、必須ではないとあります。今回も仮のURIを用意しただけです。
  • 実際のmemo typeは以下をプロパティとして持ちます。
    • title: string
    • text: string
    • favorite: boolean
  • レコードID、作成日、更新日などは、各レコード(メッセージ)のメタデータとしてWeb JS SDKが作るため、typeには含めていません。

各機能に対するWeb5インターフェイスの利用するメソッド

詳細はリンク先のAPI仕様書を、また具体的な利用例として前述のsrc/modules/memo.tsをご参照ください。

試行 / デモ

1. アプリ起動と相手のDID登録

開始ボタン押下でDIDなどを持つWeb5インスタンスを生成しています。
生成されたDID(did:ion Long-Form)をログからコピーし、それを相手のUIにて登録します。(相手のDIDはLocal Storageに保存しています)

2. Alice(Bob)によるメモの作成と読取

右下のリフレッシュボタン押下で、自分のLocal Nodeと相手のRemote Nodeからデータを取得しています。相手のLocal-Remote間でデータが同期されると、自分のUIにもそれが反映されます。

3. Alice(Bob)によるメモの更新、削除と読取

別の方式: 相手のRemote Nodeへのメッセージ送信

今回は自身のRemote Nodeにメッセージを載せ、他者に読み取り権限を与えて、他者にそれをPullしてもらう方式で組み立てました。別の方式として、相手のRemote Nodeに自身からメッセージをPushすることも可能な模様です。詳細は以下の開発ドキュメントをご参照ください。

Send DWN Records: Send to Recipient’s DWNs

今後の深掘り

今回は簡易的なDWAを作り動かすことを試すという趣旨から、出来上がったものは基本的な内容にとどまっています。以下は今後、より深掘りしていきたい内容です。

  1. Web5クラスのインスタンス化における構成周り(参考資料P.17参照)
    • ユーザーから入力されたパスコードで認証し、それを基にしたLocal NodeのApp Data Vault暗号化 (参考資料 P.24参照)
    • AppDataStoreインターフェースのデフォルト実装を継承した、未実装部分(パスフレーズ変更やバックアップ、リストア)に対応するクラスの実装
    • 1つのアプリで複数のWeb5インスタンスの作成と切り替え(プロファイルの作成と切り替えのイメージ)
      • DID、App Data Vaultの構成、Remote NodeのEndpoint構成、Local-Remote間の同期設定、Protocol Definition、App Dataが紐づく。
  2. Protocol周り
    • 階層化された複雑なstructureを要するモデルでの試行
    • Actionの設定にて、Actor(Anyone, Author, Recipient)ではなくRoleに基づくやり方
    • dwn-sdk-jsの最新バージョン(v0.2.18)で実装されていると考える、Roleに対するActionであるqueryとsubscribeの試行
    • schemaに対するJSON Schemaバリデーションの実装
  3. Messaging周り
    • Remote Nodeを提供するプラットフォーマーからのデータ秘匿対応 = プライベートなデータを暗号化したメッセージング
      • 自分だけが見れるパターン
      • 送信者と受信者だけが見れるパターン
        • DIDCommのように、自DIDと他DIDの鍵交換でKEKを生成し、それでEphemeralなCEKを暗号化してメッセージに含めて送る。
        • Remote Nodeを運用するプラットフォーマーからもデータを保護できると考える。
        • 現状では上記処理をアプリ側の責務で対応しなければならないと考える。
  4. その他
    • Web5 JS SDKとReactNativeを使った、もしくはweb5-sdk-kotlinを使ったモバイルアプリ作り
    • 同じProtocolを使う異なるアプリ間でのやり取り
    • 今後リリース予定とされるIdentity Walletの試行
    • did:dhtについて

おわりに

Web5は現在Technical Preview状態で、直近ではデフォルトのDID Methodをdid:ionからdid:dhtに変える決定が成される(つまりデフォルトはブロックチェーンレスになる)などの動きがあり、今後も注目していきたいと思います。

後編では、TBDのSSI Serviceを導入してTrust Triangle形成の試みを記事にしたいと思います。
(今の所、下図の形を取れると考えています)。

どなたかのお役に立てたならば幸いです。