Web3の技術、惑星間ファイルシステム(IPFS)について調べる
はじめに
こんにちは、yoshi です。ナビタイムジャパンでサーバーサイド開発を担当しています。
近年盛り上がっているメタバースですが、そのプラットフォームとして Decentraland や The Sandbox などが有名です。そのサービスの根幹を支えている技術の一つが分散ファイルシステムである IPFS (InterPlanetary File System) です。日本語に翻訳すると「惑星間ファイルシステム」となります。オシャレですね。
この技術は将来的にはどんなサービスでも利用できると考えられるため、この技術がどういうものなのか、セキュリティ周りも含めて調べてみました。
IPFS (InterPlanetary File System) とは
IPFS は P2P ネットワーク上で動作するコンテンツ指向型のハイパーメディア分散プロトコルです。コンテンツは特定サイズのブロックに分割され、IPFS 上に共有されます。コンテンツを取得する際は分割された各ブロックを IPFS ネットワーク上の複数のノードからダウンロードします。
コンテンツ指向型
コンテンツは SHA などでハッシュ化され、そのハッシュ値がそのコンテンツを示す ID となります。つまり、誰がそのコンテンツを作成したとしても全く同一のコンテンツであった場合は同じハッシュ値が生成され、誰が作成したかを区別しません。
例として、「テスト」という文字列を試しに SHA-256 でハッシュ化してみると以下のハッシュ値が得られます。
8A535A3F4DCD2C396DB11B7C1E54221D04375C9F9BE96BCE47DC2FDB237E86C9
この文字列が「テスト」というコンテンツの ID となります。例え他の誰かが「テスト」という文字をハッシュ化して IPFS ネットワーク上にアップロードしたとしても、ハッシュ値は同じ値となるため、私がアップロードしたデータでも、他の誰かがアップロードしたデータでも、同じものを得られるということになります。
上記の特性によって、現在主流なロケーション指向(URL を指定してデータへアクセスする方式)に比べて以下のメリットがあります。
耐障害性
データを分散して保持しているため、一つのサーバーがダウンしたとしても同じハッシュ値のデータを持っている別のサーバーからデータを取得することが可能です。負荷分散
各サーバーへの距離情報を持っており、それぞれのユーザーは自分に近いサーバーへ問い合わせるため、一つのサーバーに負荷が集中する可能性が低くなります。耐検閲性
複数のサーバーでデータを分散して保持しているため、コンテンツへの検閲が難しくなります。耐改ざん性
得られたコンテンツをハッシュ化し、コンテンツを示す ID と比較することによって正しいコンテンツかを簡単に確認できます。
ファイル共有の仕組み
自分のノードから IPFS ネットワーク上にアップロードしたコンテンツは、参照される際に他のノードにキャッシュされます。
このキャッシュからもコンテンツを参照することができ、キャッシュされているコンテンツを近くのノードから取得します。これによって高い耐障害性と負荷分散効果が得られます。
キャッシュには保持期限が設定されており、誰も参照しなくなれば各ノードからキャッシュデータが消滅します。
もし、自ノードのコンテンツを削除しても、他のノードがそのコンテンツをダウンロードし、保持(PIN)している場合はそのコンテンツは IPFS ネットワーク上に残り続けることになります。
上記のような特性を持つため、特に人気のあるコンテンツほど IPFS ネットワーク上に残りやすく、人気のないコンテンツは誰かが意図的に残そうとしない限り残りにくくなる性質になっています。
CLI で IPFS を触ってみる
各 OS 用のデスクトップアプリケーションも用意されていますが、今回は CLI で IPFS を触ってみます。
go-ipfs をインストール
$ wget https://dist.ipfs.io/go-ipfs/v0.12.2/go-ipfs_v0.12.2_linux-amd64.tar.gz
$ tar xfz go-ipfs_v0.12.2_linux-amd64.tar.gz
$ cd go-ipfs/
$ sudo ./install.sh
動作確認でバージョン表示
$ ipfs --version
ipfs version 0.12.2
IPFS リポジトリの初期化
$ ipfs init
generating ED25519 keypair...done
peer identity: xxxx
initializing IPFS node at /home/user/.ipfs
to get started, enter:
ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme
自ノードの ID がここで生成されます。
ipfs cat で IPFS 上のデータを表示できるようです。
$ ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme
Hello and Welcome to IPFS!
██╗██████╗ ███████╗███████╗
██║██╔══██╗██╔════╝██╔════╝
██║██████╔╝█████╗ ███████╗
██║██╔═══╝ ██╔══╝ ╚════██║
██║██║ ██║ ███████║
╚═╝╚═╝ ╚═╝ ╚══════╝
If you're seeing this, you have successfully installed
IPFS and are now interfacing with the ipfs merkledag!
-------------------------------------------------------
| Warning: |
| This is alpha software. Use at your own discretion! |
| Much is missing or lacking polish. There are bugs. |
| Not yet secure. Read the security notes for more. |
-------------------------------------------------------
Check out some of the other files in this directory:
./about
./help
./quick-start <-- usage examples
./readme <-- this file
./security-notes
これでもう IPFS ネットワーク上からデータを取得できています。
readme の他にも quick-start があるようなのでそれを読みながら色々実行してみます。
ファイルの追加
$ echo "hello world" > hello
$ ipfs add hello
added QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o hello
この QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o が コンテンツ ID (CID) です。だれが "hello world" というファイルを作成してもこの ID は一意に決まることになります。
ディレクトリの追加
$ mkdir testdir
$ mv hello testdir/
$ ipfs add -r testdir
added QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o testdir/hello
added QmYRMUVULBfj7WrdPESnwnyZmtayN6Sdrwh1nKcQ9QgQeZ testdir
ディレクトリの中を参照するには ipfs ls を使います。
$ ipfs ls QmYRMUVULBfj7WrdPESnwnyZmtayN6Sdrwh1nKcQ9QgQeZ
QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o 12 hello
公開ゲートウェイ
IPFS ネットワーク上にあるコンテンツにアクセスするために、ipfs コマンドを使用しました。これは IPFS ネットワークに直接アクセスするものとなりますが、公開ゲートウェイを介することでブラウザ上からもアクセス可能です。
https://ipfs.io は IPFS の公開ゲートウェイを提供しているため、https://ipfs.io/ipfs/任意のCID でアクセスすることが可能です。
試しに先ほどの hello world へブラウザからアクセスしてみると、hello world が表示されます。
https://ipfs.io/ipfs/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o
ただ、この方式ではゲートウェイを介したデータのやり取りを行うため、IPFS の分散ファイルシステムとしてのメリットを活かせていない(耐障害性、負荷分散)ことに注意が必要です。少なくともコンテンツは IPFS ネットワーク上にあるため、CID でアクセスする以上、耐検閲性、耐改ざん性は保たれます。
データの永続化について
基本的にローカルノードにデータを保持している(PIN している)限り、IPFS 上で永続化できているといえます。その他のノードでは断片データをキャッシュとして保持しており、よく参照されるデータは複数ノードのキャッシュからデータを取り出せることになります。しかしキャッシュは一定期間参照されないと削除されます(ガベージコレクション)。
つまり、人気のあるコンテンツは常に誰かが参照しようとするため、キャッシュとして永続的に残りやすく、人気のないコンテンツは一定期間のキャッシュ削除によって、PIN で保持しない限り IPFS ネットワーク上に残らないことになります。
ローカルノード以外でも PIN することでデータを保持したい場合、外部ノードで PIN する有料サービスも存在しています。また、データの継続的安定的保持自体にインセンティブを与える Filecoin のような仕組みもありますが、基本的にコストがかかってきます。
注意点として、たとえ人気がなかったとしても他の誰かが残したいと思った場合は、その人のローカルノードにダウンロード、PIN されることによっても永続的に保持されることになります。つまり、削除したいと思っても他の誰かにダウンロードされ、PIN され続ける限りそのコンテンツは削除できないことになります。
永続化に関しては、コンテンツを PIN する以外に、MFS (Mutable File System) を利用することでもガベージコレクションされないようにできます。
MFS (Mutable File System)
MFS を利用することで、CID ではなく、パスベースでファイル操作が可能になります。このパスはローカルノードでのみ有効なものなので、他のノードからデータを取得する際には CID が必要となります。
ディレクトリを作成します。
$ ipfs files mkdir /example
$ ipfs files stat /example
QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn
Size: 0
CumulativeSize: 4
ChildBlocks: 0
Type: directory
ファイルを追加してみます。
$ ipfs files write -e /example/hello hello
$ ipfs files ls /example
hello
ファイルを読むには ipfs files read を利用します。
$ ipfs files read /example/hello
hello world
ステータスを見るとディレクトリの CID が変化していることがわかります。
$ ipfs files stat /example
QmWdhpFpCiVvtxgRVCisWdNQbAbmDxFtQsY1X3DJhu42Qc
Size: 0
CumulativeSize: 121
ChildBlocks: 1
Type: directory
ファイル追加前の CID にはファイルがないことが確認できます。
$ ipfs ls QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn
ファイル追加後の CID にはファイルが追加されています。
$ ipfs ls QmWdhpFpCiVvtxgRVCisWdNQbAbmDxFtQsY1X3DJhu42Qc
QmVib14uvPnCP73XaCDpwugRuwfTsVbGyWbatHAmLSdZUS 12 hello
このように MFS を利用すれば、ユーザーは CID ではなく、ファイルパスでコンテンツを扱えるようになります。
MFS によって追加されたコンテンツはベストエフォート(最新コンテンツのみ保持対象)で保持されます。コンテンツそのものをきちんと保持したい場合は CID を明示的に PIN する必要があります。
IPNS (InterPlanetary Name System)
コンテンツの内容が変わるとハッシュ値も変わってしまうため、変更前の CID では新しいコンテンツへアクセスできません。これでは不便なことがあるので、コンテンツを更新しても継続して参照できるような仕組みとして、IPNS(InterPlanetary Name System)があります。ここのサンプルと同じ手順で IPNS の挙動を確認してみます。
まずは IPFS デーモンを起動させます。
$ ipfs daemon
もう一つのターミナルでコンテンツを作成します。
$ echo "Hello IPFS" > hello
コンテンツをリポジトリに追加します。
$ ipfs add hello
added QmUVTKsrYJpaxUT7dr9FpKq6AoKHhEM7eG1ZHGL56haKLG hello
CID が得られました。中身を確認します。
$ ipfs cat QmUVTKsrYJpaxUT7dr9FpKq6AoKHhEM7eG1ZHGL56haKLG
Hello IPFS
生成したコンテンツの CID を IPNS に公開します。
$ ipfs name publish /ipfs/QmUVTKsrYJpaxUT7dr9FpKq6AoKHhEM7eG1ZHGL56haKLG
Published to k51qzi5uqu5dhwoapocczu6i21oh16mr43laj6hkbgytdug971w4x2jyozxguu: /ipfs/QmUVTKsrYJpaxUT7dr9FpKq6AoKHhEM7eG1ZHGL56haKLG
得られた k51qzi5uqu5dhwoapocczu6i21oh16mr43laj6hkbgytdug971w4x2jyozxguu が IPNS の ID となります。
IPNS でコンテンツを確認してみます。
$ ipfs cat /ipns/k51qzi5uqu5dhwoapocczu6i21oh16mr43laj6hkbgytdug971w4x2jyozxguu
Hello IPFS
CID を指定したときと同じコンテンツが得られました。次はコンテンツの中身を変更してみます。
$ echo "Hello again IPFS" > hello
変更したコンテンツをリポジトリに追加します。
$ ipfs add hello
added QmaVfeg2GM17RLjBs9C4fhpku6uDgrEGUYCTC183VrZaVW hello
新たな CID が得られたため、前回の CID では変更されたコンテンツを参照できません。
CID を IPNS に再公開します。
$ ipfs name publish QmaVfeg2GM17RLjBs9C4fhpku6uDgrEGUYCTC183VrZaVW
Published to k51qzi5uqu5dhwoapocczu6i21oh16mr43laj6hkbgytdug971w4x2jyozxguu: /ipfs/QmaVfeg2GM17RLjBs9C4fhpku6uDgrEGUYCTC183VrZaVW
IPNS の k51qzi5uqu5dhwoapocczu6i21oh16mr43laj6hkbgytdug971w4x2jyozxguu に再公開されました。
IPNS でコンテンツを再確認してみます。
$ ipfs cat /ipns/k51qzi5uqu5dhwoapocczu6i21oh16mr43laj6hkbgytdug971w4x2jyozxguu
Hello again IPFS
同じ ID で更新されたコンテンツが確認できました。
このように、IPNS を使えば、コンテンツの更新ごとに参照する CID を変更する必要はなくなります。
ちなみに IPNS で生成されるキーは ipfs key gen キー名で生成でき、publish のときにキーを指定することでそのキーに紐づける CID を指定することができるので、複数のコンテンツを別々に管理したい場合にはこれを使えば良さそうです。
キーを生成します。
$ ipfs key gen OtherKey
k51qzi5uqu5dgwn48fvwdeyl2xevzf7k6jqdt6u8nt1mq0o0zgm7e1oga0gv48
公開時にキーを指定します。
$ ipfs name publish --key=OtherKey QmaVfeg2GM17RLjBs9C4fhpku6uDgrEGUYCTC183VrZaVW
Published to k51qzi5uqu5dgwn48fvwdeyl2xevzf7k6jqdt6u8nt1mq0o0zgm7e1oga0gv48: /ipfs/QmaVfeg2GM17RLjBs9C4fhpku6uDgrEGUYCTC183VrZaVW
指定したキーで参照します。
$ ipfs cat /ipns/k51qzi5uqu5dgwn48fvwdeyl2xevzf7k6jqdt6u8nt1mq0o0zgm7e1oga0gv48
Hello again IPFS
DNSLink
CID の更新を隠蔽する方法として、 IPNS の他にも DNSLink を利用することもできます。
DNSLink を利用するにはドメインが必要ですが、_dnslink.example.com のようなアドレスに TXT レコードを設定すれば公開ゲートウェイからドメイン指定でアクセス可能になります。
TXT レコードの中身は以下のように CID を記載します。
dnslink=/ipfs/CID
以下のサイト(libp2p.io)は IPFS 上に Web ページをアップロードしているものを公開ゲートウェイ経由で表示しています。
https://ipfs.io/ipns/libp2p.io/
DNSLink から CID を得るには dig コマンドを使います。
$ dig +short TXT _dnslink.libp2p.io
_dnslink.libp2p-website.on.fleek.co.
"dnslink=/ipfs/QmRzrPpwdfNHj8SSDzsnxjg8tKcZMeHVzbdK4Z8UoU2B2k"
これで CID を得られました。つまり、以下の2つのリンクは(現時点では)同じコンテンツが得られることになります。
https://ipfs.io/ipns/libp2p.io/
https://ipfs.io/ipfs/QmRzrPpwdfNHj8SSDzsnxjg8tKcZMeHVzbdK4Z8UoU2B2k/
試していないのでたぶんできるはずという話ですが、DNSLink に IPNS を記載していれば、IPNS の更新だけでコンテンツの更新が可能になるはずです。
セキュリティについて
IPFS は転送時には暗号化を行っていますが、コンテンツ自体は暗号化しません。つまり、CID を知られてしまえばそのコンテンツを誰でも取得できるということです。これは IPFS を軽量に保ち、ベンダーロックインを発生させないための意図的な仕様であると明記されています。
コンテンツの暗号化自体はデータをアップロードする側で行い、ダウンロード側で復号する必要があるということですが、永続的に保持するようなデータの場合、将来の技術革新を考えると古い暗号化ファイルは復号されてしまう危険性があるため、暗号化が絶対安全なものでないことに注意が必要なようです。
コンテンツを参照するノードを制限する方法として、プライベート IPFS ネットワークの作成が可能となっており、アクセスが許可されるノードのみがコンテンツを共有できるようにできます。これによって情報公開の範囲はコントロールできますが、ネットワーク規模が小さい場合、IPFS で得られるメリットを享受できない可能性があり、このことを念頭に置いて設計する必要がありそうです。
IPFS の使い道
永続化のコストをかけない場合、IPFS の仕組み上、よく参照されるデータはキャッシュとして残りやすく、参照されないデータは自然と消えていくことになるため、簡単なところではスパイク的にリクエストが急増するような静的データを近くのユーザーと共有することで負荷分散を図ることが考えられそうです。
その他、様々な活用方法をここで提案してありました。
おわりに
新しい分散ファイルシステムである IPFS について調べてみました。まだα版の技術ですが、将来性が期待されており、メタバースプラットフォームのみならず、Fleek のように既に IPFS をサービスとして提供している企業もあります。使い方によっては負荷分散、サーバーコスト削減を目的としてサービス適用できそうな技術であると感じていますので、引き続き調査していこうと思います。
参考URL
https://ipfs.io/
https://ja.wikipedia.org/wiki/InterPlanetary_File_System
https://www.mki.co.jp/knowledge/column90.html
https://www.cxr-inc.com/TECHNOLOGY_base.php?id=29
https://ipfs-book.decentralized-web.jp/
https://dnslink.io/
https://spotlight.soy/detail?article_id=4s1fnrn91