本記事ではThe did:key Method v0.7の実装を見ていく。
The did:key Method
DID Methodとはなんぞや、という話は省略する。DID-COREのリンクを貼るので参照して欲しい。
The did:key Method (以下did:key) はエフェメラル用途向けに考案されたDID Methodで、DID Documentの名前解決にDLTや共有ストレージのようなバックエンドを必要としない。「key」という名前の通り、did:keyのDID文字列は単一の公開鍵と全く同じ情報量を持ち、DID DocumentはDID文字列(⇔公開鍵)から一意に導出される。DID文字列に情報が全て含まれるので、誰でも、たとえオフラインでも名前解決が可能なのが最大の特徴だ。
導出方法
ここからは実際の導出の過程を見ていこう。
公開鍵 ←→ DID文字列
DID文字列は公開鍵から次のように一意に導出される:
didString pubKeyType rawPubKeyBytes =
"did:key:" ++ multibase base58btc (multicodec pubKeyType rawPubKeyBytes)
ここでmultibaseは文字列をbase変換してから基数(及びアルファベット)を表すプレフィックスを付与する関数で、base58-bitcoinの場合は"z"がプレフィックスになる。また、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
ところで、
- DID文字列と公開鍵は等価である
- did:keyにおいて、DID Documentの導出にDID文字列以外の情報は使えない
のだから、did:keyではDID Documentも公開鍵と等価であるということになる。あるいはDID Documentに公開鍵一つ分の情報しか載せられないと言い換えても良い。
となるとわざわざDID Documentを導出する必要もあまりないように思うが、まあフォーマット変換のようなものだと思って見ていこう。他のDID Methodと互換なのは大事だ。
実際の例を見る前に、まずどんな形式になるかを予想してみよう。先程のDIDでいえば、だいたい次のような感じになるはずだ:
{
"@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ではどうなっているのか?
{
"@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はこんな感じになる:
{
"@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をサポートするのかがわかる一覧がないと実際の実装は難しいように思う。