相変わらずTwitterの仕様変更が激しいですね。最近では、Twitter未登録の人にツイートを見せない施策や、ツイート表示回数制限の導入、TweetDeck無料版の廃止などの施策が進められています。一連の騒動を受けてMetaがTwitter対抗SNSのThreadsを出したり、Misskey・Mastodonの登録者が急増するなどTwitterに代わるSNSを取り巻く状況が活性化しています。
そんな中Twitter代替に数えられるBlueskyにも注目が集まっています。自分はBlueskyが採用しているAT Protocolに関して関心があり、先日もdid:plcに関する記事を書きました。未だよく全体像を把握できていないのですが、AT Protocol規格を読む中で見かけるCBORやCID、DAG-CBORに関して調べたためまとめます。
CBOR
概要
CBORはConcise Binary Object Representationの略です。バイナリデータの汎用フォーマットであり、JSONを参考にしてシンプルに作られています。仕様書としては2013年のRFC7049が初出で、その後出てきた問題に関して言及して改めて書き直したRFC8949が最新です。
JSONはご存知でしょうか?JSONとは以下のようなフォーマットです。
{
"key1":"value1",
"key2":[1,2,3],
"key3":{
"key3_1":1
}
}
これはCBORへほとんど1対1の翻訳が可能であり、0xA3646B6579316676616C756531646B65793283010203646B657933A1666B6579335F3101となります。JSONとの対応をわかりやすく書くと、以下のような感じです。
(煩雑になるので最初の3要素だけ色枠で囲みました。なお、JSONのCBOR表示には https://cbor.me/ を利用しました。)
図を観察すると気づくと思いますが、CBORはデータの前に型とサイズを表記したメタデータを配置し、続けてデータ本体を置くという記法を取っています。正式には、型とサイズを表記したメタデータを「ヘッド(head)1」、ヘッドの後に続くデータのことを「データアイテム(data item)」と言います。
以下は私の独断と偏見でピックアップした、CBORの特徴です。
バイナリ形式
JSONと同じデータをダブルクオーテーションやカンマなど不要で表現できるので、サイズの削減が期待できます。また、CBORはバイナリ形式なのでバイナリデータをシンプルに表現できます。例えば、JSONで画像ファイル名の一覧と画像データを組にして表現するにはどうすればいいでしょうか?理想的にはこうしたいはずです。
{
"001.jpg":001.jpgのバイナリデータ,
"002.jpg":002.jpgのバイナリデータ,
...
}
しかし、JSONでサポートされているデータ型はbool, 実数, 文字列なので、バイナリデータをそのまま書き込めません。そのため、JSONを作る側はbase64などで文字列にエンコードして入れ、読む側はbase64文字列をデコードして画像を取り出すなど工夫が必要です。テキスト形式なのでバイナリデータ型を持っていないのは当然ですね。一方、CBORはデータ型として、byte stringをサポートしているので、上のようなデータ構造をそのまま書き込めます。
不定長の配列・マップ・byte string・text string
概要の節で、データアイテムの前には型とサイズを表すヘッドが付加されると書きました。CBORでは配列型・マップ型・byte string型・text string型の場合、サイズを「不定長」とすることもできます。不定長データの終わりを表す”break”というヘッドが出現したら、その不定長データは終了です。
例えば、さっきのJSON例の一番外側のマップを不定長マップとしたCBORは0xA3BF646B6579316676616C756531646B65793283010203646B657933A1666B6579335F3101FFです。A3が「要素数3のマップ」という意味だったのに対し、BFという「不定長のマップ」に変わっており、最後にFFというbreakが付け加わっています。
不定長のbyte stringとtext stringは、配列チックな書き方になります。例えば、”key1″という文字列はCBORの固定長text stringで0x646B657931と表現されます。これを不定長のtext stringで表す場合は、0x647F616B63657931FFとかけます。7Fが不定長の文字列、61が1文字の固定長文字列、6Bが”k”、63が3文字の固定長文字列、657931が”ey1″です。
不定長配列と不定長マップはネスト可能なのに対し、不定長のbyte stringとtext stringはネスト不可能です。これは、ネストさせたところで結局最終的に全部フラットに結合されるので、ネストする意味が無いからです。
リアルタイムにデータが増えていき、増えたそばからストリーミング的に送信したいような場合に役立ちそうです。
値に「日付である」「URLである」などのタグを付加できる
データに日付時刻を入れたい場合を考えます。JSONの場合、”date”というキーにはRFC3339の日付時刻を入れることにする、などとJSONデータ外で合意するなどの方法を取ると思います。CBORの場合、「タグ」という概念があり、こういった目的に使えます。タグは配列やマップのようなコンテナであり、1個だけデータを保持することができます。タグは2^64-1種類作る事ができ、それぞれのタグに独自の意味を持たせることができます。
例えば、タグ0はRFC3339の日付データを意味し、保持されているデータはtext stringでなければなりません。”2023-07-08T12:00:00+09:00″というtext stringにタグ0を付加した場合、0xC07819323032332D30372D30385431323A30303A30302B30393A3030となります。C0がタグ0を表すヘッド、データアイテムが78 19すなわち25文字の固定長文字列です。
何番のタグがどういう意味かという紐づけは、IANAのConcise Binary Object Representation (CBOR) Tagsにて管理されています。ただし、規格上で、CBORのエンコーダやデコーダはすべてのタグの意味を理解する必要は無いとされています。CBORを作るアプリケーションと読むアプリケーションが同じ開発者によって作られているなら、タグなど使わなくてもそれぞれの値の意味は理解できることがあるからですね。
まとめ
- CBORはJSONを参考にして作られた、シンプルな汎用バイナリフォーマット
- 不定長の配列・マップ・文字列・バイナリがサポートされている
- データに「日付である」「URLである」などのタグを付けられる
厳密な仕様に関してはRFC8949、データ構造の図示は英語版Wikipedia、デコーダの実装に関してはRFCからCBORのデコーダーを作る | blog.ojisan.io、ライブラリはCBORオフィシャルサイトを参照してください。
CID
概要
CIDとはデータのハッシュ値を曖昧さなく表現するものです。データのハッシュ値と一口に言っても、
- どんなハッシュ関数を使っているのか
- ハッシュ値はバイナリで表現されているのか、base64でエンコードして表示されているのか、それとも他の形式なのか
などの情報がないと、受け手が理解することができません。CIDはハッシュ値に加え、これらのメタデータを付加したものになります。
定義
<cidv1> ::= <multibase-prefix><multicodec-cidv1><multicodec-content-type><multihash-content-address>
multibase??multicodec??multihash??content-address??となると思いますので、それぞれに関して説明します。
multibase
base64というエンコード形式があります。これは、64個の文字で任意のデータを表現するエンコーディングで、文字しか受け付けないプロトコルで無理やりバイナリデータを送ったりするのに使ったりします。では、base58btcはどうでしょうか?base256emojiは?実は、世の中にはたくさんのbase◯◯があります2。文字列だけ送り付けてこれはjpg画像を表すよ、と言われてもbase何でエンコードしているのかを指定しないと画像を見れません。
multibaseはbase◯◯の◯◯を表すメタデータを先頭にくっつけて、残りの部分がデータ本体という記法です。例えば、base64で
TXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw==
と表されるデータは、multibaseではbase64を表す”M”を付加して
MTXVsdGliYXNlIGlzIGF3ZXNvbWUhIFxvLw==
になります。
multicodec
multicodecはバイナリコードとその数値が意味するものの雑多なテーブルです。multibaseやmultihashとは異なり、エンコード形式ではありません。CIDにおいて、multicodec-content-typeはハッシュ値を計算されているデータが何形式なのかを表します。例えば、JSONデータのハッシュ値を計算している場合、multicodec-content-type=0x0200となります。なお、multicodec-cidv1は0x01で固定です。
multihash
multihashはハッシュ関数のバイナリコード、ハッシュ値のサイズ(byte)、ハッシュ値を結合した、ハッシュ値のエンコード形式です。ハッシュ関数のバイナリコードは前述のmulticodecに載っています。例えば、sha1で”multihash”のハッシュ値を計算した場合、multihashでの表現はsha1を表す0x11、ハッシュ値の長さである20 byte、ハッシュ値0x88c2f11fb2ce392acb5b2986e640211c4690073eを結合して
0x111688c2f11fb2ce392acb5b2986e640211c4690073e
となります。
CIDにおけるmultihash-content-addressはmultihashで表現されたハッシュ値ということになります。
例
sha2-256でハッシュ値が0x6e6ff7950a36187a801613426e858dce686cd7d7e3c0fc42ee0330072d245c95と計算されるデータがある場合、そのCIDは、base58btcで表現する場合、
zb2rhe5P4gXftAwvA4eXQ5HJwsER2owDyS9sKaQRRVQPn93bA
となります。
DAG-CBOR
概要
DAG-CBORは他のDAG-CBORデータのCIDを含んだCBORデータです。仕様書には書かれていませんが、DAGと名がつくので、CIDによる他データへの参照が有向非巡回グラフとなる用途を想定しているはずです。DAG-CBORではCBORとCIDに対して、以下の制限が加わっています。
- タグ42が付与されたデータはCIDを意味するものとする。他のタグはサポートしない。
- マップのキーはstringでなければならない3
- CIDはCBORのbinary string型で記入する。
- CIDのmultibaseプリフィックスは0x00、すなわち生バイナリでなければならない。
- エンコーダーは、数値データの表現に何byte使うかなどの不定性を排するため、Deterministically Encoded CBORに従わなければならない。
- CBORのヘッドのうち、メジャータイプ7に含まれるものは、Float/False/True/Nullのみを許容する。
- 浮動小数点数は64bitで表現する。
- NaN, Infinity, -Infinityは無効なデータとする。
- 1個のCBORデータには1個のオブジェクトしかいれてはならない。
所感
CBORはAT Protocol以外の場面でも役立ちそうですね。
次はAT ProtocolのRepository周りに関して調べようと思います。
コメント