IPFS入門 : 新たなP2Pハイパーメディア分散プロトコル


提供:Bogdan Burcea

IPFSがあれば、他の全てのものが一定のやり方で見えるようになり、全てをすっかり置き換えられることに気づく – Juan Benet

IPFSへのあまり技術的でないアプローチ

執筆:John Lilic

本稿の後半では、同僚のChristian Lundkvist博士が踏み込んだ技術的概要を紹介していますので、その導入となる大まかな考え方をこのセクションでお話ししたいと思います。

IPFSは、バージョン管理された科学的データを巡回する高速システムの構築を目的として、Juan Benetの取り組みによって始まりました。バージョン管理が行われていると、ソフトウェアの変更履歴を追跡できるのです(Gitのような仕組み)。その後、IPFSは分散された永続的なWebとして考えられるようになりました。「IPFSとは、あらゆる端末を同じファイルシステムに接続することを意図した分散ファイルシステムです。ある点でこれはWebの本来の目的と似ていますが、実はIPFSは、Gitオブジェクトを交換する単一のBitTorrentスウォームの方にもっと似ています。IPFSはインターネットの新たな主要サブシステムになるかもしれません。適切に構築されれば、HTTPやそれ以上のものを補完したり、置き換えたりできる可能性もあります。途方もない考えのように思えますが、まさに途方もないシステムです」[1

IPFSの中核はバージョン管理されたファイルシステムであり、ファイルを受け取って管理し、どこかに保管も行って、バージョン履歴を追跡することができます。IPFSでは、各ファイルのネットワークでの動きも分かりますから、分散ファイルシステムでもあるのです。

IPFSでは、データやコンテンツがネットワークをどう動くかについて、BitTorrentと性質的に似たルールがあります。IPFSのファイルシステムのレイヤには、以下のような非常に興味深い特性があります。

  • 完全に分散化されたWebサイト
  • 元サーバがないWebサイト
  • 完全にクライアント側のブラウザで動作するWebサイト
  • どのサーバとも通信しないWebサイト

コンテンツ・アドレッシング

IPFSは、オブジェクト(画像、記事、映像)を参照する時にどのサーバに保管されているかを調べるのではなく、全ての参照においてファイルのハッシュを使います。考え方としては、ブラウザで特定のページにアクセスしたい場合、IPFSはネットワーク全体に対して「このハッシュに対応するこのファイルを持っているか?」と問い合わせを行い、該当するIPFSのノードがファイルを返すことで、ページにアクセス可能になるという仕組みです。

IPFSはHTTPレイヤでコンテンツ・アドレッシングを使っています。これは、「コンテンツをlocationで指定する識別子を生成するのではなく、コンテンツ自体の表現でコンテンツを指定します」、と宣言するやり方です。つまり、コンテンツがアドレスを決定するのです。IPFSにファイルが追加されると、それを暗号化したハッシュが生成されるという仕組みなので、ファイルの表現は非常に小さく安全なものとなります。従って、同じハッシュを持つ別のファイルを誰かが偶然見つけてそれをアドレスとして使うようなことはできません。IPFSでは、ファイルのアドレスは通常、ルートオブジェクトを識別するハッシュで始まり、走査するパスが次に続きます。サーバではなく特定のオブジェクトと通信して、そのオブジェクト内のパスを調べるのです。

ファイル探索におけるHTTPとIPFSの比較

HTTPには、識別子に所在が含まれているという便利な特性があるので、ファイルを提供しているコンピュータを見つけてそれと通信することは簡単です。これは便利な性質であり大抵は問題ないのですが、オフラインの場合や、ネットワークの負荷をできる限り少なくしたい大規模分散環境では、うまい方法とは言えません。

IPFSでは、このステップは以下の2つに分かれています。

  1. コンテンツアドレッシングでファイルを識別する
  2. 問い合わせと発見:ハッシュを得たら、自身がつながっているネットワークに対して「このコンテンツを持っているノードは?(ハッシュ)」という問い合わせを行い、該当するノードに接続してダウンロードを実行する

その結果、IPFSは、高速なルーティングが可能なPeer-to-Peer(P2P)型のオーバーレイとなっています。

詳細については、アルファ版のデモ映像をご覧ください。

例で見るIPFS

執筆:Christian Lundkvist博士

技術的に見ると、IPFS(InterPlanetary File System:惑星間ファイルシステム)は、分散ハッシュテーブルGitバージョン管理システム、BitTorrentといった実績のあるインターネットテクノロジの統合体です。IPFSではP2Pスウォームが生成され、IPFSオブジェクトの交換ができるようになっています。IPFSオブジェクトの総体は、Merkle DAGとして知られる暗号化認証されたデータ構造を形成し、このデータ構造を基にして他の多くのデータ構造が作られます。以下ではIPFSオブジェクトとMerkle DAGについて紹介し、IPFSを使って作られる構造の例を見ていきます。

IPFSオブジェクト

IPFSは本質的に、IPFSオブジェクトを検索し共有するためのP2Pシステムです。IPFSオブジェクトとは、以下2つのフィールドをもつデータ構造です。

  • Data:サイズが256kBより小さい構造化されていないバイナリデータの塊。
  • Links:Link構造体の配列。他のIPFSオブジェクトへのリンク。

Link構造体には、以下3つのフィールドがあります。

  • Name:Linkの名前。
  • Hash:リンクされたIPFSオブジェクトのハッシュ。
  • Size:リンクされたIPFSオブジェクトの累積サイズ。リンクの追跡を含む。

Sizeフィールドは、主にP2Pネットワーキングを最適化するために使用されますが、概念上は、論理構造に必要なものではないため、本稿では特に扱いません。

IPFSオブジェクトは通常、Base58でエンコードされたハッシュによって参照されます。例えば、QmarHSr9aSNaPSR6G9KFPbuLV9aEqJfTk1y9B8pdwqK4Rqというハッシュを持つIPFSオブジェクトを、IPFSコマンドラインツールで見てみましょう(これは自宅で試してくださいね)。

> ipfs object get QmarHSr9aSNaPSR6G9KFPbuLV9aEqJfTk1y9B8pdwqK4Rq
{“Links”: [{
“Name”: “AnotherName”,
“Hash”: “QmVtYjNij3KeyGmcgg7yVXWskLaBtov3UYL9pgcGK3MCWu”,
“Size”: 18},
{“Name”: “SomeName”,
“Hash”: “QmbUSy8HCn8J4TMDRRdxCbK2uCCtkQyZtY6XYv3y7kLgDC”,
“Size”: 58}],
“Data”: “Hello World!”}

全てのハッシュが「Qm」で始まっていることにお気づきでしょうか。その理由は、このハッシュが実はmultihashの形式であるからです。つまり、ハッシュの先頭2バイトで、ハッシュ自体がハッシュ関数とハッシュの長さを示しているのです。上記の例では、先頭2バイトを16進数で表すと1220となりますが、12はこれがSHA-256ハッシュ関数であること、20はハッシュの長さが32バイトであることを意味しています。

データと、名前付きのリンクがあることで、IPFSオブジェクトの集合はMerkle DAGの構造を取ります。DAGは有向非巡回グラフを意味し、Merkleはこれが暗号化認証されたデータ構造であり、コンテンツのアドレス指定に暗号化ハッシュを用いていることを示しています。このグラフで閉路を持つことができない理由について考えるのは、皆さんの練習問題としておきましょう。

このグラフ構造を目に見えるようにするため、IPFSオブジェクトを視覚化してみたいと思います。グラフでは、ノードにDataがあり、Linksは他のIPFSオブジェクトへ向かう有向グラフのエッジ、LinkのNameはグラフのエッジに付けられたラベルとなります。以下の図は、上記の例を視覚化したものです。


それでは、IPFSオブジェクトで表すことのできる様々なデータ構造の例を見ていきましょう。

ファイルシステム

IPFSは、ファイルとディレクトリから構成されるファイルシステムを簡単に表すことができます。

小さなファイル

小さなファイル(< 256kB)をIPFSオブジェクトで表すと、dataがファイルのコンテンツ(および小さなヘッダとフッタ)で、リンクがないためlinksは空配列となります。ファイル名がIPFSオブジェクトの一部ではないことに注意してください。つまり、コンテンツが同じで名前の異なる2つのファイルは、IPFSオブジェクトの表現が同じになるため、生成されるハッシュも同一なのです。

ipfs addというコマンドを使うと、IPFSに小さなファイルを追加することができます。

chris@chris-VBox:~/tmp$ ipfs add test_dir/hello.txt
added QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG test_dir/hello.txt

ipfs catを使えば、上記IPFSオブジェクトのファイルのコンテンツを見ることができます。

chris@chris-VBox:~/tmp$ ipfs cat QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG
Hello World!

ipfs object getで基本構造を見てみましょう。

chris@chris-VBox:~/tmp$ ipfs object get QmfM2r8seH2GiRaC4esTjeraXEachRt8ZsSeGaWTPLyMoG
{“Links”: [],
“Data”: “\u0008\u0002\u0012\rHello World!\n\u0018\r”}

このファイルを視覚化すると、以下のようになります。

大きなファイル

大きなファイル(> 256kB)は、256kBより小さいファイルの塊に対するリンクのリストと、このオブジェクトが大きなファイルの表現であることを示す最小限のDataで表されます。ファイルの塊に対するリンクは、名前として空文字列を持っています。

chris@chris-VBox:~/tmp$ ipfs add test_dir/bigfile.js
added QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w test_dir/bigfile.js
chris@chris-VBox:~/tmp$ ipfs object get QmR45FmbVVrixReBwJkhEKde2qwHYaQzGxu4ZoDeswuF9w
{“Links”: [{
“Name”: “”,
“Hash”: “QmYSK2JyM3RyDyB52caZCTKFR3HKniEcMnNJYdk8DQ6KKB”,
“Size”: 262158},
{“Name”: “”,
“Hash”: “QmQeUqdjFmaxuJewStqCLUoKrR9khqb4Edw9TfRQQdfWz3”,
“Size”: 262158},
{“Name”: “”,
“Hash”: “Qma98bk1hjiRZDTmYmfiUXDj8hXXt7uGA5roU5mfUb3sVG”,
“Size”: 178947}],
“Data”: “\u0008\u0002\u0018* \u0010 \u0010 \n”}

ディレクトリ構造

ディレクトリは、ファイルや他のディレクトリを表すIPFSオブジェクトへのリンクのリストで表されます。リンク名は、ファイル名やディレクトリ名です。例えば、次のようなディレクトリtest_dirのディレクトリ構造を考えてみてください。

chris@chris-VBox:~/tmp$ ls -R test_dir
test_dir:
bigfile.js hello.txt my_dir
test_dir/my_dir:
my_file.txt testing.txt

hello.txtmy_file.txtというファイルの両方に文字列Hello World!\nが含まれ、testing.txtというファイルには文字列Testing 123\nが含まれています。

このディレクトリ構造をIPFSオブジェクトとして表すと、以下のようになります。

Hello World!\nを含むファイルの自動重複排除に注意してください。このファイル内のデータはIPFS内の1つの論理的な場所に格納されているだけ(ハッシュによるアドレス指定)です。

IPFSコマンドラインツールを使えば、シームレスにディレクトリのリンク名を追って、ファイルシステムを横断できます。

chris@chris-VBox:~/tmp$ ipfs cat Qma3qbWDGJc6he3syLUTaRkJD3vAq1k5569tNMbUtjAZjf/my_dir/my_file.txt
Hello World!

バージョン管理されたファイルシステム

IPFSは、Gitで使われているデータ構造を表して、バージョン管理されたファイルシステムを実現可能にします。Gitコミットオブジェクトについては、Git Bookに説明があります。IPFSコミットオブジェクトの構造は、本稿の執筆時点では完全には規定されておらず、議論が続いています

コミットオブジェクトの主な特性は、前のコミットを指すparent0、parent1などといった名前の1つ以上のリンクと、object(Gitではtreeと呼ばれます)というリンク名の、コミットによって参照されるファイルシステム構造を指す1つのリンクを持つということです。

一例として、従来のファイルシステムのディレクトリ構造を2つのコミットと共にご紹介しましょう。1つ目のコミットは元の構造です。2つ目のコミットでは、もともとのHello World!ではなくAnother World!と表示されるように、ファイルmy_file.txtが更新されています。

自動重複排除機能があるので、2つ目のコミットの新しいオブジェクトは、メインディレクトリ、新しいディレクトリmy_dir、そして更新されたファイルmy_file.txtだけだということにも気を付けてください。

ブロックチェーン

これはIPFSで最も面白いユースケースの1つです。ブロックチェーンには自然なDAG構造があり、過去のブロックはハッシュによって後のブロックと常にリンクされています。Ethereumで使われているような高度なブロックチェーンも関連づけられた状態データベースを持っており、そのデータベースはMerkle-Patricia tree構造で、IPFSオブジェクトを使ってエミュレートすることもできます。

前提としているのはブロックチェーンの単純すぎるモデルで、各ブロックが含んでいるデータは以下のとおりです。

  • トランザクションオブジェクトのリスト
  • 前のブロックへのリンク
  • 状態のツリー/データベースのハッシュ

それから、このブロックチェーンは次のようにIPFSでモデル化することができます。

状態データベースをIPFSに配置する際に得る重複排除機能が見られます。2つのブロック間には変更された状態のエントリのみが明示的に格納されなくてはなりません。

ここで興味深い点は、ブロックチェーン上にデータを格納することと、ブロックチェーン上にデータのハッシュを格納することとの違いです。Ethereumプラットフォームでは、関連づけられた状態データベースにデータを格納するためにかなりのコストがかかります。状態データベースの肥大化(”ブロックチェーンの肥大化”)を最小限に抑えるためです。そのため、大きめのデータは、データそのものではなく、データのIPFSハッシュを状態データベースに格納するのが一般的なデザインパターンです。

関連づけられた状態データベースを持つブロックチェーンが既にIPFSで表されている場合、ブロックチェーン上にハッシュを格納することとブロックチェーン上にデータを格納することとの違いは、やや不鮮明になります。とにかく全てがIPFSに格納されているので、ブロックのハッシュに必要なのは、状態データベースのハッシュだけです。この場合、IPFSリンクがブロックチェーンに格納されていれば、シームレスにそのリンクをたどって、まるでデータがブロックチェーン自体に格納されているかのように、データにアクセスできます。

しかし、それでもオンチェーンとオフチェーンのデータストレージを見分けることができます。新しいブロックを作る際にマイナーが処理しなければならないものを見て、区別します。現在のEthereumネットワークでは、状態データベースを更新するトランザクションをマイナーが処理する必要があります。それを行うため、どこが変更されても更新できるように、マイナーには完全な状態データベースへのアクセスが必要です。

従って、IPFSで表されるブロックチェーンの状態データベース内で、やはりデータに”オンチェーン”か”オフチェーン”かのタグを付けなければならないでしょう。”オンチェーン”データは、マイニングを行うためにマイナーがローカルに保持する必要があり、このデータは直接トランザクションの影響を受けます。”オフチェーン”データは、ユーザが更新しなければならず、従ってマイナーがこれに触れる必要はありません。