How Internet Computer Responses Are Certified as Authentic(日本語翻訳)
インターネットコンピュータの応答が真正であると認定される方法
インターネットコンピュータ上で動作するcanisterスマートコントラクトからの応答が、どのようにして真正であると認証されるのかを技術的に説明します。
Joachim Breitner氏
インターネットコンピュータのブロックチェーンと対話するユーザーは、自分が得た応答をどのようにして信頼することができるでしょうか。言い換えれば、自分が本当にインターネットコンピュータと話していることを、どうやって確認できるのでしょうか?
まず、理想的な世界ではどうなのかを考えてみましょう。完全に信頼できる環境(自分のコンピュータや会社のネットワークなど)で動作しているサービスと対話していると想像してみてください。このブログでは、ショーのチケットを予約するためのサービスを想定しています。
つまり、あなたはそのサービスとやり取りして、次回のショーのチケットを予約します。そのサービスとネットワークが信頼できるものであれば、それだけで、そのショーの5A席を本当に予約したという安心感と確信を持つことができます。しかし、実際には、インターネット上のサービスに接続している場合が多い。もし、あなたと相手のサービスの間にいる誰かが、あなたのリクエストとそれに対応するレスポンスを操作しようとしたらどうなるでしょうか?例えば、座席を間違えたり、予約したのに実際には予約していないと言われたりするかもしれません。逆に、実際には購入していないのに、購入したと言われる可能性もあります。
まさに今回の問題のようなものです。インターネット上では、公開鍵暗号方式を用いた解決策が知られています。サービスは秘密鍵を持っています。これは上の図の右にある青い鍵です。そして、サービスがあなたのリクエストに応答するとき、秘密鍵を使ってその応答に署名します。署名は、上図の青いリボンで表されます。
ユーザーであるあなたは、サービスの対応する公開鍵を知っています。その公開鍵を使って、レスポンスの署名を検証することができます。そして、その応答がそのサービスから来たものであることを確認し、自分が本当にチケットを予約したことを知ることができます。これが、インターネット上のサービスを利用するときの常識です。
それがインターネットコンピュータになると、似ているようで少し違います。相手のサービスは、独自の秘密鍵を持つフルスタックのサービスサーバーではなく、キャニスターのスマートコントラクト(インターネットコンピュータ上で動作するアプリケーション)で実行されるコードに過ぎず、自分で暗号化を行うことはありません。その代わり、サブネットと呼ばれるもので実行されています。サブネットとは、キャニスターを共同で実行するノードの集まりで、この例では予約代理店がそれにあたります。
サブネット全体で、先ほどのような署名を作成することができます。サブネットを構成するノードは複数あり、その中には悪意のあるノードもあるかもしれませんが、署名を作成できるのは、過半数超のノードが署名内容に同意した場合のみです。つまり、一部のノードが悪意を持っていたとしても、偽の署名を作成することはできないのです。そのため、インターネットコンピュータからサブネットの有効な署名付き応答を受信すると、一部のノードが悪意を持っていたとしても、ユーザーはこれが合意された応答であることを知ることができます。
このような署名形態を閾値署名と呼びます。ここで興味深いのは、これらの署名を作成するには多少のコストがかかるということです。このような閾値署名を作成するためには、ノードが協力して通信する必要があります。そのため、1秒間に数千件のリクエストが発生する可能性がある中で、個々のレスポンスに署名を付与するのは規模的に無理があります。
この問題を解決するために、レスポンスは個別には署名されません。ネットワークが行うことは、このブログ記事の目的のために仮称するとして、サブネットの「document」にすべてのレスポンスをリストアップすることです。
ここにあるように、これらのレスポンスのどこかに、この例のユーザーであるKimがサービスにショーのチケットの予約を依頼したとき、Kimの予約は成功し、座席は5Aであることが記されています。また、Larryや他のユーザーに対するレスポンスもあります。理論的に可能なことは、サブネットによるこの閾値署名鍵からの署名を持つ、この「document」全体を取得し、ユーザーに発送することです。ユーザーは、自分の回答がその文書の中にあることを確認できます。これは理想的ではありません。というのも、すべての回答がこのdocumentに記録されるため、大きなサイズになってしまうからです。さらに、Larryが全く関係のないサービスに行ったリクエストに対する回答を、Kimが見る必要はない。
そこで、Kimが見るべきでない部分を削除してdocumentを再編集し、Kimに関係のある部分のみを掲載することができる。署名はそのまま有効になります。つまり、Kimは、このdocumentを見て、彼女の要求に対する回答が、5A席のチケットを手に入れたことであり、署名が正しいことを確認することができるのである。リダクション(documentの削減)は、Kimが見るべきではない情報を隠し、documentを小さくする。この効果は、原理的にはドキュメントの圧縮や画像の圧縮に似ています。このようにして、Kimは、自身が知る必要のあることだけを伝える、適度なサイズのレスポンスを得ることができる。
しかし、考慮しなければならない点があります。話しかけられているこのサブネットはサブネット5と呼ばれていますが、これはサブネットが1つではないことを意味しています。インターネットコンピュータは、サブネットの数が常に増えていく無限に拡張可能なブロックチェーンです。上の画像では、Kimはサブネット5に対応する公開鍵を知っています。これだけ多くのサブネットがあれば、すべてのユーザーがすべてのサブネットに対応するすべての公開鍵を必要とするのは理想的ではありません。このアイデアは、Kimが実際には1つの公開鍵しか持っておらず、Kimがこの1つの公開鍵を信頼すれば、少量のデータで、インターネット・コンピュータから送られてくるすべてのデータを検証するのに十分であるというものです。
この方法は、上のスライドでオレンジ色で示されているインターネットコンピュータの1つの公開鍵が、ルートサブネットと呼ばれるある特定のサブネットの公開鍵であることを利用しています。ルートサブネットは、他のサブネットと同じように、悪意のあるノードもあれば、誠実なノードもあります。ルートサブネットの特徴は、ユーザーがその公開鍵を知っていることです。
このルートサブネットは、インターネットコンピュータのすべてのサブネットのリストをそのdocumentに含んでいます。ご覧のように、インターネットコンピュータにはサブネット5があり、青い鍵を持っていることがわかります。
ユーザーへの応答とともに、サブネットはそのdocumentの編集されたコピーを含めることができます。これは、ルートサブネットからサブネット5に信頼を委ねることであり、委譲(delegation)と呼ばれています。これでユーザーは、このdocumentを見て、ユーザーが話しているサブネット(サブネット5)の公開鍵を見つけることができ、ユーザーはそこに青い鍵を見つけることができます。このようにして、ユーザーはすべてのサブネットの公開鍵を知る必要はなく、1つのサブネットの公開鍵だけを知っていればよいという問題が解決されるのです。
このようにして、サブネットはインターネットコンピュータに代わって、アップデートコールと呼ばれるものに対するレスポンスを認証します。アップデートコールとは、ユーザーからのリクエストがインターネットコンピュータに届き、コンセンサスを経て、ブロックに含まれるなどしたものです。アップデートコールは、キャニスターのサービスの状態を変更することができます。これはもちろん、ショーを予約する際には重要なことです。最終的にはレスポンスを取得し、そのレスポンスが認証されると、ユーザーは先ほど説明した方法でコンテンツを確認することができます。
この認証プロセス全体は、キャニスターのアプリケーション・コードやクライアント側のアプリケーション・コードに対して完全に透過的に行われます。しかし、アップデートコールは、コンセンサスなどを経る必要があるため、2秒程度の時間がかかります。
レイテンシーを最小限に抑えるために、クエリコールも用意されています。クエリコールとは、キャニスターの状態を変更しないメソッドの呼び出しです。状態を変更しないので、決定論的な順序で実行したり、新しい状態を必要とするすべてのノードで実行したりする必要はありません。クエリコールは、サブネット全体ではなく、1つのノードが応答することができます。
つまり、クエリコールは非常に高速で、数秒ではなくミリ秒単位で実行できます。しかし、ここで問題があることにお気づきでしょうか。サブネット全体(subnet5)が信頼でき、サブネット全体(subnet5)だけがこの青い署名を行うことができると言いましたが、サブネット上の1つのノードが悪意を持っている可能性があります。その悪意のある単一ノードがあなたのクエリコールに応答したとき、その応答が正しいことをどうやって知ることができるでしょうか?単一ノードは単独では署名できないので、その単一ノードは青い鍵を使って応答に署名できません。では、クエリコールを行っても、インターネットコンピュータから認証されたレスポンスを得るにはどうすればよいのでしょうか。
これは、認証された「変数」と呼ばれる機能を使って可能です。重要なのは、これにはキャニスター側の協力が必要だということです。アップデートコールを認証するために、キャニスターのコードやアプリケーションの開発者は特別なことをする必要はなく、そのまま動作します。しかし、これらのクエリコールを保護し、認証された変数を使用して保護するには、キャニスターの協力が必要です。
これがどのようなものかを説明します。ユーザーが予約を行うと、キャニスターは、システムの特別な領域に、「Kimがシート5Aを予約した」という状態を記録します。これは基本的に、「Kimが5A席を予約したことを覚えておいてください」とシステムに伝えており、キャニスターの通常の状態とは別に保存されている。実際には、先ほどのすべての応答を記録した同じdocumentに書き込まれている。このdocumentは、この青いリボンを使って、サブネット全体で署名されていることを覚えておいてください。
これは、クエリコールが発生したときに役立ちます。例えば、1日後にKimが自分の座席番号を忘れてしまい、クエリコールを使ってキャニスターに自分の座席が何かを尋ねたいとします。クエリコールは、どの席だったかを確認するだけであれば、アップデートコールを必要としないため、非常に優れたユースケースです。クエリを処理するとき、キャニスターは、キャニスター自身がシステムに記憶するように依頼した情報 (つまりKimは座席5Aを持っている)以外のすべてを再編集した、サブネットのdocumentの再編集されたコピ ーをシステムに求めることができます。これで、キャニスターは、Kim へのレスポンスに、「証明書」と呼ばれるもの(つまり、再編集されたdocument)を含めることができる。
そしてKimはキャニスターからのレスポンス(You have seat 5A)が正しいかどうかを実際にチェックすることができる。もちろん、これは署名をチェックすることを意味しますが、そのための既存のコードがあります。これは、アップデートコールに対するレスポンスの有効性をチェックするのと同じメカニズムである。また、Kimは情報が古すぎないかどうかもチェックする必要があります。なぜなら、攻撃者がKim氏に有効なレスポンスを与えたとしても、それは1週間前に有効だっただけで、状況は変わっているからです。このように、ユーザー(具体的には、ユーザー側のクライアントコード)がチェックしなければならないことがいくつかありますが、それらをチェックしているときは、レスポンスが正しいことがわかります。
システムをシンプルに保つという制約のために、ちょっとした複雑さが生じています。インターネットコンピュータのようなシステムを設計する場合、常にトレードオフの関係にあります。システムを少し複雑にして使い勝手(キャニスターから見た場合)を良くするか、それとも必要最低限の機能だけを提供してキャニスターに負担をかけ、システムをシンプルに保つことで安全性や管理性を確保するか。
この場合、先ほど説明した機能を使用するために、キャニスターには32バイトのストレージしか許されていません。Kimが5A席を予約したことを伝えるには32バイトで十分かもしれませんが、何人もの人が席を予約するようになると、32バイトでは足りなくなりますが、これは根本的な問題ではありません。この問題を解決するために適用できるトリックは、ほんの数枚前のスライドで、サブネットが1つの署名を多くのレスポンスに使用する方法を見たときに適用したものとほとんど同じです。ここでも、再編集されたdocumentの1つが使用されています。ここでできることは、キャニスターが、Kimがショーを予約したときに、Kimがシート5Aを持っているという情報を、独自のdocumentに保存することです。また、キャニスターは、他の人々がショーの座席を予約しているという情報も持っています。そして、documentの内容を一意に識別するために、ドキュメントの暗号ハッシュを計算し、その暗号ハッシュの保存と認証をサブネットに依頼します。
今、Kimがサービスにどの席を持っているかを尋ねると、サービスは、暗号ハッシュなどの情報を含むサブネットからのdocumentを返信し、さらにキャニスターのデータを再編集したコピーを返信することで、あなたが持っている席5Aという情報が本当に正しいかどうかを検証することができる。前回同様、青の署名とKimが持っているオレンジの鍵を結びつける、この小さな委譲(delegation)が必要です。
これらがすべて揃っていれば、ユーザーが取得したレスポンスから、ユーザーがすでに持っている鍵まで、信頼の連鎖をたどることができます。ユーザーは、このレスポンスが、キャニスターと左下(上図)のdocumentの再編集されたコピーに含まれていることを確認できます。そのdocumentのルートハッシュまたは暗号ハッシュを再計算し、それがサブネットからの再編集されたdocumentに含まれていることを確認することができます。そのdocumentの青色の署名を確認し、これが本当に委任されたそのサブネットの対応する鍵で署名されていること、つまり右下のルートサブネットからの再編集されたdocumentであることを確認することができます。そのdocumentはオレンジ色の鍵を使って署名されています。ユーザーはその署名を、ユーザーが持っている公開鍵と照合することができます。このようにして、システムは、配信されたレスポンスを認証し、悪意のある単一ノードからでもそれを計算し、ユーザーに配信し、ユーザーがそのレスポンスが正しいかどうかを検証することができるのです。
情報を編集しても、署名を検証できるという興味深い特性を持つこれらのdocumentを、もう少し詳しく見てみましょう。ここで使用されているメカニズムは、Merkle木と呼ばれる有名な暗号ツールです。混乱を避けるため、このブログ記事での「再編集可能」という言葉は例え話であることに注意してください。技術的な意味での再編集可能な署名を指しているわけではありません。
これらはまさにMerkleデータ構造であり、目新しいものではありません。ここでは、このMerkleツリーがどのように機能し、どのように利用できるのかを、少しだけ説明したいと思います。最初に使った例えはdocumentでしたが、ここでは別の例えの方がうまくいきます。ハードドライブのファイルシステムで、フォルダと名前のリストがあります。フォルダの中にはさらにフォルダがあり、データの入ったファイルもあります。これが、このツリーの概念モデルです。今はツリー状になっていて、サブネットはこのツリーの1つを管理しています。これがステートツリーと呼ばれる所以で、あらゆる種類の情報が含まれています。ユーザーからのリクエストに対するレスポンスが書かれています。
また、クエリコールが認証される際に調べられる認証データや、現在の時刻、クロスネットメッセージングなど、インターネットコンピュータ全体を動かすために必要な多くの内部データなど、さまざまな情報を持っています。これで、このツリーはツリー型になりました。上図では、木が下に向かって伸びているように描いています。コンピュータサイエンスでは、木はなぜか下に向かって伸びるので木の中にはデータ用の隅(訳者:分からなかったのでそのまま記載)のようなものがあるのです。左上には、この木が作られた時間が表示されています。ここで、すべてのデータ、ノード上のすべてのテーブル、およびその形状を含むツリー全体を一意に識別する暗号ハッシュを定義したいと思います。
そのためのちょっとした技術的ステップとして、ツリーを二分木にする必要があります。ご覧のように、ツリーは複数回分岐していますが、実際には二分木にした方がきれいです。つまり、すべてのノードには1つまたは2つの子があり、その性質を利用して、分岐を簡単に複数の二分木に置き換えることができます。次に必要なのは、その木の暗号ハッシュをどのように計算するかを定義することですが、これはボトムアップのアプローチで行うことができます。データを持つ葉に対しては、SHA-256のようなデータのハッシュを使えばいい。次に、ラベルがあります。例えば、左下に「certified_data」があります。そして、そのサブツリーのハッシュは、ラベルのハッシュと、それ以下のすべてのハッシュです。
バイナリノードについても、同様のことが行われます。これらのバイナリノードの1つのハッシュは、左と右のサブツリーのハッシュです。このようにして、下から上に向かってずっとハッシュを定義するルールが定義され、ルートにもハッシュが存在します。こうすることで、32バイトの小ぶりな数字でツリー全体を識別できるようになったのです。そのハッシュに署名があれば、その署名を使ってツリー内のすべてを検証することができます。次に、リダクションを行う必要があります。例えば、キャニスターabcde-fghの認証データがたまたまCAFFEEだったことをKim氏に暴露したいとします。また、現在の時間がそれらを持っている時間であることも示したいかもしれません。
そして、これらの2つの情報に関係のないサブツリーをすべて削除することができます。しかし、削除されたサブツリーのハッシュ(上の小さなオレンジ色の注釈で表される)も記憶しておかなければなりません。なぜそのようなことが必要なのでしょうか?枝刈りされた木をユーザーに渡すと、枝刈りされた場所のハッシュがすべて含まれているので、ユーザーは以前と同じように、下から上に向かって個々のノードのハッシュをすべて再計算することができ、したがってルートノードのハッシュも再計算することができます。
そして、そのルートハッシュ(ルートノードのハッシュ)の青い署名を取り、ユーザーがその署名を検証することができます。ユーザーがツリー全体を持っていなくても、ユーザーが署名を検証してツリー全体に署名することができるわけです。サブネットでは、このように情報を再編集できるdocumentが使用されていますが、より多くの場所で使用されています。例えば、ルートサブネットでもステートツリーを使用していますが、どのサブネットが存在し、公開鍵が何であるかという追加情報を持っています。また、予約代行会社の例では、クエリコールが認証されているため、キャニスター自体がMerkle構造の1つを持ち、予約された座席に関するすべての情報を1つの小さなハッシュに格納し、認証されたデータとしてシステムに接続することができます。
以上、Merkle木の仕組みについてまとめてみました。非常に汎用性の高いツールであり、インターネットコンピュータでも効果的に利用できる。
最後に、クエリコールを安全にするために認証データの恩恵を受けたい場合、インターネットコンピュータ上のキャニスター開発者、いや、アプリケーション開発者として行わなければならない手順を説明したいと思います。これにはキャニスターの助けが必要で、あなたがしなければならない開発もある程度あります。まず、どのようなクエリを保護する必要があるのかを考え、それに適したMerkleizedデータ構造を考えなければなりません。ツリーを紹介しましたが、ツリーはキーバリューを探すようなものや、ファイルシステムに似たものにはとても便利です。もっと洗練された、データを集約したり選択したりする必要がある場合は、それとは違うものが必要になるかもしれません。これはアプリケーションによります。単純なケースでは、このようなツリーが有効でしょう。そして、そのツリーを通常のキャニスターの状態の一部として維持しなければなりません。これはインターネットコンピュータ上で動作するプログラムのメインメモリで実行されていて、そこに反映させる必要のある状態の一部を変更するアップデートコールが来るたびに、Merkle木構造を更新しなければなりません。その構造の中のすべてのデータを識別するルートハッシュを再計算し、それをキャニスターの認証データとしてシステムに書き出さなければなりません。
また、クエリメソッドを処理する際には、クエリメソッドに対するレスポンスを計算し、キャニスター内のMerkle木構造を調べて不要なものをすべて削除する必要があります。基本的には、実際に与えようとしているレスポンスが、先に保存したハッシュに含まれていることを証明する「証人」と呼ばれるものを計算します。また、前回のアップデートコールの際に書き込んだハッシュが期待通りのものであることを示す証明書(再編集されたdocument)をシステムに渡してもらう必要があります。そして、キャニスター内のMerkle木構造の再編集された部分であるレスポンスと、ルートハッシュがデータ構造のルートハッシュであることを証明するものをユーザーに送付します。
クライアントコードに関しては、あなたのサービスのために専用のクライアントを書くことが期待されます。なぜなら、あなたがしなければならないいくつかのサーバー固有のことがあるからです。そうしないと、悪意のあるノードが古い結果を出して、1週間前には本当だったかもしれないが、今はもう本当ではないと信じさせてしまうからです。次に、そのデータが本当に通信相手のキャニスターのデータであることを確認します。そうしないと、攻撃者は同じような状態の別のキャニスターをサブネット上に設置することができます。例えば、5A席ではなく7D席であるという情報を持つ偽の予約代理店を設置し、それに基づいてレスポンスを構築することができます。
そのため、実際に会話しているキャニスターかどうかを確認する必要があります。次に、キャニスターから受け取った編集済みのMerkleツリーから、アプリケーション固有のデータ構造を調べます。そのルートハッシュを再計算し、システムデータにあるハッシュと比較します。最後に、アプリケーション固有のデータの内容が、クエリのパラメータやレスポンスと一致しているかどうかを確認しなければなりません。クエリのパラメータとレスポンスの両方を念頭に置く必要があります。これらのチェックをクライアントコードの一部として行った場合、信頼できない可能性のあるノードから送られてきたとしても、そのレスポンスを信頼することができます。
先に述べたように、アップデートコードの場合は、このようなことを心配する必要はありません。これがあまりにも複雑な場合や、クエリがMerkle構造でサポートされているフォーマットに合わない場合は、クエリコールを使用してセキュリティ保証を低下させるか、アップデートコードを使用してパフォーマンスを多少低下させるかのいずれかの選択肢があります。開発者が特別なことをしなくても、より多くの保証と悪意のあるノードからの保護が得られるように、クエリコールの安全性を高める方法を検討しています。これについては現在検討中ですので、今後の情報にご期待ください。
それまでの間、私が紹介した技術的な詳細について知りたい方は、「Internet Computer Interface Specification」の「The System State Tree」、「Certification」、「Certified Data」のセクションにすべて記載されています。
さらにご不明な点がございましたら、開発者フォーラムにて、インターネットコンピュータ上で革新的なアプリケーションを構築する皆様をサポートさせていただきます。