DID Method 探訪：The did:key Method

本記事では[The did:key Method v0.7](https://w3c-ccg.github.io/did-method-key)の実装を見ていく。

## The did:key Method

DID Methodとはなんぞや、という話は省略する。[DID-CORE](https://www.w3.org/TR/did-core/)のリンクを貼るので参照して欲しい。

The did:key Method (以下did:key) はエフェメラル用途向けに考案されたDID Methodで、DID Documentの名前解決にDLTや共有ストレージのようなバックエンドを必要としない。「key」という名前の通り、did:keyのDID文字列は**単一の公開鍵と全く同じ情報量**を持ち、DID DocumentはDID文字列(⇔公開鍵)から一意に導出される。DID文字列に情報が全て含まれるので、誰でも、たとえオフラインでも名前解決が可能なのが最大の特徴だ。

## 導出方法

ここからは実際の導出の過程を見ていこう。
### 公開鍵 ←→ DID文字列

DID文字列は公開鍵から次のように一意に導出される：

```Haskell
didString pubKeyType rawPubKeyBytes =
  "did:key:" ++ multibase base58btc (multicodec pubKeyType rawPubKeyBytes)
```

ここで[multibase](https://github.com/multiformats/multibase)は文字列をbase変換してから基数(及びアルファベット)を表すプレフィックスを付与する関数で、base58-bitcoinの場合は"z"がプレフィックスになる。また、[multicodec](https://github.com/multiformats/multicodec)はバイナリデータにコーデックを表すプレフィックスを付与する関数で、例えばEd25519公開鍵の場合は0xedがプレフィックスになる(unsigned varintの形式で付与するため、バイナリ上は0xed01)。

did:keyのspecificationに書いてある例で実際に導出してみよう。pubKeyTypeはEd25519VerificationKey2018、pubKeyはbase58-bitcoinで `B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u` とする。Hexで書けば`0x94966b7c08e405775f8de6cc1c4508f6eb227403e1025b2c8ad2d7477398c5b2`だ。

Ed25519VerificationKey2018のmulticodecプレフィックスは`0xed`、unsigned varintだと`0xed01`になる。これをバイナリの先頭に付与するので、`0xed0194966b7c08e405775f8de6cc1c4508f6eb227403e1025b2c8ad2d7477398c5b2` がmulticodec後のバイナリとなる。

これのbase58-bitcoinを取ると`6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH` となる。multibaseのプレフィックス"z"と"did:key"を先頭につければ、`did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH`。これで完成だ。specificationの例と一致することを確認して欲しい。

DID文字列に公開鍵の情報が完全に入っているので、逆変換するだけでDIDから公開鍵を得ることも可能だ。つまりdid:keyにおいては、**DID文字列と元となった公開鍵は等価である**。

### DID文字列 → DID Document

ところで、

1. DID文字列と公開鍵は等価である
2. did:keyにおいて、DID Documentの導出にDID文字列以外の情報は使えない

のだから、did:keyでは**DID Documentも公開鍵と等価である**ということになる。あるいはDID Documentに公開鍵一つ分の情報しか載せられないと言い換えても良い。

となるとわざわざDID Documentを導出する必要もあまりないように思うが、まあフォーマット変換のようなものだと思って見ていこう。他のDID Methodと互換なのは大事だ。

実際の例を見る前に、まずどんな形式になるかを予想してみよう。先程のDIDでいえば、だいたい次のような感じになるはずだ：

```json:予想されるDIDDocument
{
  "@context": "https://www.w3.org/ns/did/v1",
  "id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
  "verificationMethod": [
    {
      "id": "#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
      "type": "Ed25519VerificationKey2018",
      "controller": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
      "publicKeyBase58": "B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u"
    }
  ],
  "authentication": ["did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"],
  "assertionMethod": ["did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"],
  "capabilityDelegation": ["did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"],
  "capabilityInvocation": ["did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"]
  "keyAgreementKey": ["did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"]
}
```

verificationMethodに元の公開鍵を入れておき、すべてのverification relationshipでそれを参照する。verification relationshipを限定したりはできないので、did-coreで定義されているものを全部入れるしかないだろう。まさしく公開鍵一つ分の情報しかないDID Documentと言える。

さて、実際のspecificationではどうなっているのか？

```json:The&#x20;did&#x3a;key&#x20;Method&#x20;v0.7&#x20;EXAMPLE&#x20;2より引用

{
  "@context": "https://w3id.org/did/v1",
  "id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
  "publicKey": [{
    "id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
    "type": "Ed25519VerificationKey2018",
    "controller": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
    "publicKeyBase58": "B12NYF8RrR3h41TDCTJojY59usg3mbtbjnFs7Eud1Y6u"
  }],
  "authentication": [ "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" ],
  "assertionMethod": [ "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" ],
  "capabilityDelegation": [ "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" ],
  "capabilityInvocation": [ "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" ],
  "keyAgreement": [{
    "id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#zBzoR5sqFgi6q3iFia8JPNfENCpi7RNSTKF7XNXX96SBY4",
    "type": "X25519KeyAgreementKey2019",
    "controller": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
    "publicKeyBase58": "JhNWeSVLMYccCk7iopQW4guaSJTojqpMEELgSLhKwRr"
  }]
}
```

**あれ、なんかちょっと違う……**

まず、publicKeyプロパティはverificationMethodプロパティの古い表現だ。将来的にverificationMethodに置き換えられるとのことなので一旦置いておこう。

問題はkeyAgreementに**なぜか生えているもう一つの公開鍵**である。何だこれは？

調べたところ、Ed25519VerificationKey2018はそのまま鍵交換には使えないため、X25519KeyAgreementKey2019に変換する必要があるらしい。この変換は比較的安全だが、数学的な証明を欠いていると考える実装者もいるとのことだ(参考：https://w3c-ccg.github.io/did-method-key/#key-derivation-lacks-proof) 。

ここがdid:keyで一番もやもやするところで、どうやら元となる公開鍵の種類によって利用可能なverification relationshipが変わるらしい。例えばX25519KeyAgreementKey2019でもDIDを作ることができるが、その場合のDID Documentはこんな感じになる：

```json:X25519KeyAgreementKey2019で作ったDIDのDocument
{
  "@context": "https://www.w3.org/ns/did/v1",
  "id": "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
  "keyAgreement": [
    {
      "id": "#z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
      "type": "X25519KeyAgreementKey2019",
      "controller": "did:key:z6LSeu9HkTHSfLLeUs2nnzUSNedgDUevfNQgQjQC23ZCit6F",
      "publicKeyBase58": "4Dy8E9UaZscuPUf2GLxV44RCNL7oxmEXXkgWXaug1WKV"
    }
  ]
}
```
見ての通り、鍵交換にしか使えないDIDだ。これでいいのなら、正直Ed25519の場合も単にkeyAgreementを省けば良いように思うが……。

この辺りのさじ加減は導出する人(あるいはDID→DID Documentの変換ライブラリ)の鍵への理解度に拠るところが大きそうに思う。今の所「その鍵はどのverification relationshipに使えるか」というリストは出ていないように思うので、ここが現状最も大きな課題になりそうだ。

## まとめ

did:keyでは、DID、公開鍵、DID Documentはすべて同値となる。公開鍵と同値なので、DIDの寿命は公開鍵の寿命と同じだし、他のDID MethodのようなDocument更新・非有効化といったアクションは非対応だ。また、DID DocumentをDID単体から導出できるので、DLTのようなバックエンドは必要ない。オフラインでも使える軽量かつ使い捨てのDIDと考えれば良さそうだ。

実装を追ってみた感想としては、DID Documentの生成が実はそんなに自明じゃないな……と感じた。verification relationshipは元となる公開鍵のタイプに依存するので、**どの鍵タイプがどのverification relationshipをサポートするのか**がわかる一覧がないと実際の実装は難しいように思う。