Develop with pleasure!

福岡でCloudとかBlockchainとか。

IONのDIDを作ってみる

IONはMSが中心になって開発したDIDのプロトコルの1つ(did:ion)。実体は、アンカリング先のブロックチェーンBitcoin、Content-Addressed Storage System(CAS)ノードにIPFSを用いたSidetreeプロトコルになる。この他にアンカリング先をEthereumにしてSidetreeを実装したElement(did:elem)とか、他にもFabricやAmazon QLDB、S3を使った実装もあるみたい。

今回は、そんなION(正確にはSidetree)で発行されるDIDを実際に作ってみる。

DIDの作成

Sidetreeでは、以下の3つの鍵ペアを生成する。

  • 署名鍵:DID自体に関連付けられるメインの鍵で、DIDのユースケースにおける署名や認証に用いる鍵。
  • Update Key:DIDの更新とUpdate Key自体の更新に使用する鍵。
  • Recovery Key:DIDの更新、失効とUpdate Key、Recovery Key自体の更新に使用する鍵。

これらの鍵を利用して、DIDのCreate / Update/ Recover / Deactivateといった操作をサポートする。鍵は、Bitcoinと同じ楕円曲線secp256k1の鍵で、デジタル署名スキームはECDSA。

DIDを作成する場合、まず最初に上記の3つの鍵ペアを生成する。

続いて、DID Documentを作成する。DID DocumentにはDIDに関連付けられる公開鍵のリスト(↑の署名鍵)と、serviceを定義できる。serviceは、このDIDの保持するエンティティのドメインのエンドポイントや、Identity Hubのエンドポイントなどを指定できる。

次に、DIDのステートを変更するためのDID State Patcheを作成する。具体的には、DID Documentの内容を元に以下のようなreplace Patch ActionのJSONオブジェクトを作成する(今回はserviceは未定義)。

[
  {
    "action": "replace",
    "document": {
      "publicKeys": [
        {
          "id": "signing-key",
          "publicKeyJwk": {
            "crv": "secp256k1",
            "kty": "EC",
            "x": "LyZoDfdajWdfTlAqUT8V8Kb3Xy0Rc9PVXoiGpS82prs",
            "y": "vgV-yaevK2o0SXobM996p6RLCurb97ySnEj5mLBd5B0"
          },
          "purposes": []
        }
      ],
      "services": []
    }
  }
]

次にUpdate Keyと作成したPatchデータからDeltaオブジェクトを生成する↓

{
  "patches": [
    <↑のPatchオブジェクト>
  ],
  "updateCommitment": "EiB4CgS-uYVMAEKJOI1rV061MTUPqJW8tzjOCDOrBHERjA"
}

ここでUpdate Keyはそのまま使われるのではなく、Json Web Key形式のJCSで正規化されたJSONデータをSHA256ハッシュし、それをMultihashでエンコードしたものを更にBase64エンコードしたものを使用する。つまりUpdate Keyに対するコミットメントを指定することになる。

※ 基本的にDID Documentで公開する必要のある署名鍵以外(Update KeyおよびRecovery Key)は、登録時にはそのコミットメント値が使用される。

続いて、↑のDeltaオブジェクトのハッシュとRecovery Keyを使ってSuffixオブジェクトを生成する↓

{
  "deltaHash": "EiCUyXdZZrJH18glwLiuSpFM_CT5LRP7SRqcTcDnNRHEOw",
  "recoveryCommitment": "EiAN0dDol63ocIfiQQK9TiPYoOd6lXM4pr7JzpVj7h4VXw"
}

ここでも、Recovery Keyのコミットメントが使われる。また、Deltaオブジェクトのハッシュも、これらのコミットメントと同様の方法で計算される。

そして最後に、↑のSuffixオブジェクトのハッシュ値がDIDの値になる。

IONの場合、DIDの形式としてShort FormのDIDとLong FormのDIDの2通りの表現があり、Long FormのDIDはDeltaオブジェクト、Suffixオブジェクト両方を展開したJSONデータから作られる。つまり、DIDドキュメントを含むすべてのデータで構成される。そのため、ブロックチェーンやIPFSなどにアンカリングされていないDIDでも、Long Form形式のDIDであれば、そのデータを使ってDIDの検証がそのままできる。これは、そもそもDIDをブロックチェーンやIPFSにアンカリングせずに利用可能になるというメリットや*1、アンカリングまでの時間的な遅延の解消などに有用な仕組み。

↑のように、SidetreeはDIDへの変更をPatcheという形で定義して管理しようとしているのが分かる。UpdateRecoverという操作では、変更内容を定義したDeltaオブジェクトを作成し、現在有効なUpdate Key / Recovery Keyのコミットメントに対応する公開鍵とそのデジタル署名を提供することで、DIDに対する変更を適用するようになっている。発行時のDIDに対して、順番にパッチを適用していけば、最新のDIDの有効性が検証できるみたいな感じかな。

Rubyで実装してみた

なかなかドキュメントだけ見てても理解が深まらないので、↑の処理をRuby実装してみた。ロジックの詳細はコード参照

require 'sidetree'

recovery_key = Sidetree::Key.generate
update_key = Sidetree::Key.generate
signing_key = Sidetree::Key.generate(id: 'signing-key')

document = Sidetree::Model::Document.new(public_keys: [signing_key])

did = Sidetree::DID.create(document, update_key, recovery_key, method: Sidetree::Params::METHODS[:ion])

puts "DID: #{did.short_form}"
=> "did:ion:EiC4gAQxXVCnbGQKFW7wgr0nbrOxXsF5CnglU8j1sXARaA"

puts "Long-Form DID: #{did.to_s}"
=> "did:ion:EiC4gAQxXVCnbGQKFW7wgr0nbrOxXsF5CnglU8j1sXARaA:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJzaWduaW5nLWtleSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJyU2RyX3dMSno5WEVFRHFkRGo4RjZCeDNKanlXR3RVZjNJY1c0OWJxQ21VIiwieSI6IlBwTUtvTXpmVHoxSVdnRDlyU3lUMC1Ya2NKTzFIb2JtWmtXMG1zM3lvaE0ifSwicHVycG9zZXMiOltdfV0sInNlcnZpY2VzIjpbXX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpRDMtOXE1ZjRYUTEwWWQySlNzM1QxYTh4Vloycmw2VFNic0xYYVdzVVBHbUEifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUFHblhKellkUDREcUk2blRnZjRSblFQQjRVaUVVS3pCZldWQlVRRXlkZ3FRIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlCVy1PRWRMLUtWYWo4NG42OEhDclRpcWNFd1RFRFZ6Z1I4bDhsb2tQc2NrdyJ9fQ"

op = did.create_op

puts "DID suffix data: #{op.suffix.to_h.to_json}"
=> "DID suffix data: {"deltaHash":"EiAGnXJzYdP4DqI6nTgf4RnQPB4UiEUKzBfWVBUQEydgqQ","recoveryCommitment":"EiBW-OEdL-KVaj84n68HCrTiqcEwTEDVzgR8l8lokPsckw"}"

puts "Document delta: #{op.delta.to_h.to_json}"
=> "Document delta: {"patches":[{"action":"replace","document":{"publicKeys":[{"id":"signing-key","publicKeyJwk":{"crv":"secp256k1","kty":"EC","x":"rSdr_wLJz9XEEDqdDj8F6Bx3JjyWGtUf3IcW49bqCmU","y":"PpMKoMzfTz1IWgD9rSyT0-XkcJO1HobmZkW0ms3yohM"},"purposes":[]}],"services":[]}}],"updateCommitment":"EiD3-9q5f4XQ10Yd2JSs3T1a8xVZ2rl6TSbsLXaWsUPGmA"}"

DIDの作成や操作方法は分かってきたので、次は、IPFSやBitcoinへのアンカリング方法を確認したい。

*1:ただ、アンカリングしないとそのDIDが最新かどうかは検証できない。こういった特性が必要な場合はアンカリングが必要になる。まぁ多くのユースケースではDIDの有効性よりVCの有効性を検証することの方が重要なんじゃないかとも思えるけど。