
ZKの最も巧妙な応用:Tornado Cashの原理とビジネスロジックを概観する
TechFlow厳選深潮セレクト

ZKの最も巧妙な応用:Tornado Cashの原理とビジネスロジックを概観する
TornadoCashは、出金者と入金者の関連性を隠蔽することができ、利用者数が非常に多い場合には、まるでにぎわった市街地のように、犯人が群衆に紛れ込めば警察が追跡できなくなるのと同じである。
著者:Faust、Geek web3
序論:最近、Vitalik氏とある学者たちが共同で新論文を発表し、Tornado Cashがどのようにしてマネーロンダリング防止の仕組みを実現しているかについて言及した(要するに、出金者が自分の入金記録が不正資金を含まない集合に属していることを証明する方法)。しかし、その論文にはTornado Cashのビジネスロジックや原理に関する詳細な解説が欠けており、読んでもよく分からない状態になっている。
また注目に値するのは、Tornado Cashのようなプライバシー重視プロジェクトこそが、真にZK-SNARKアルゴリズムの「ゼロ知識性」を利用している点である。一方、多くのZKを掲げるRollupは、ZK-SNARKの「簡潔性」しか使っていない。多くの場合、Validity Proof(有効性証明)とZK(ゼロ知識証明)の違いが混同されてしまいがちだが、Tornado CashはZKの応用を理解するための極めて優れた事例である。
筆者はたまたま2022年にWeb3Caff ResearchにてTornadoの原理に関する記事を執筆しており、今回はその一部を抜粋・拡張して整理し、皆様が体系的にTornado Cashを理解できるようにする。
「トルネード」の原理
Tornado Cashはゼロ知識証明を活用したミキサー(混在)プロトコルであり、旧バージョンは2019年から運用され、新バージョンは2021年末にベータ版が開始された。旧バージョンのTornado Cashは基本的に非中央集権化されており、オンチェーンのスマートコントラクトはオープンソースでマルチシグによる管理も存在せず、フロントエンドコードもオープンソースとしてIPFSネットワーク上にバックアップされている。旧版の構造の方がシンプルで理解しやすいため、本稿では旧バージョンを中心に解説を行う。
Tornado Cashの主なアイデアは以下の通り:多数の入金・出金行為を混ぜ合わせ、ユーザーがTornadoにトークンを預けた後、ZK証明(ZK Proof)によって自分が確かに預け入れを行ったことを示し、新たなアドレスから引き出すことで、入金アドレスと出金アドレスの関連性を断ち切る。

より具体的に言えば、Tornado Cashはガラス製の箱のようなものであり、多くの人がコインを中に投入している。誰がコインを入れたかは見えるが、これらのコインは非常に均質化されており、見知らぬ人物が箱から1枚のコインを取り出したとしても、それが誰のものだったのかを特定するのは困難である。

このような状況は日常的に見られる。例えばUniswapのプールでETHをスワップする際、どの提供者が渡したETHを受け取ったのかを知ることはできない。なぜなら、流動性を提供した人の数が多すぎるためである。ただし違いは、Uniswapを使うたびに他のトークンを対価として支払う必要があり、資金を「秘匿的」に第三者に譲渡することはできない点だ。一方、ミキサーでは出金者が預け入れの証明さえ提示できればよい。
入金と出金の操作を同質的に見せるために、Tornadoの各プールでは、すべての入金アドレスが同じ額を預け、すべての出金アドレスが同じ額を引き出すようにしている。たとえば、あるプールに100人の預入者と100人の出金者がいる場合、彼らが誰であるかは公開されているが、お互いに関連性はなく、それぞれが預けた額も引き出した額も同一である。これにより、金額による関連性の推定が不可能となり、資金の流れを追跡できなくなる。明らかに、これはマネーロンダリングに都合の良い環境を提供している。

しかし重要な問題がある:出金者が引き出し時に、どうやって自分が預け入れたことを証明するのか? 出金を要求するアドレスは、すべての入金アドレスとは無関係であるため、どのようにしてその資格を判断すればよいのか。最も直接的な方法は、出金者が自身の入金記録を明かすことだが、それでは身元が漏洩してしまう。ここでゼロ知識証明が活躍するのである。
出金者はZK Proofを提示することで、「自分がTornadoコントラクトに入金記録を持っていること」かつ「その入金はまだ引き出されていないこと」を証明し、出金手続きを進めることができる。ゼロ知識証明自体がプライバシー保護を実現しており、外部からは「この出金者は確かに資金プールに入金した」という事実は分かるが、どの入金者に対応するかは分からない。

「私はTornadoの資金プールに入金した」ことを証明するということは、「私の入金記録はTornadoコントラクト上で確認できる」ということに変換できる。Cnを入金記録とすると、問題は次のように整理される:
Tornadoの入金記録集合が{C1, C2, …, C100, …}であるとき、出金者Bobは自分が持つ秘密鍵を使って、その中のいずれかのCnを生成したことを証明するが、ZKを通じてCnがどれであるかは明かさない。
ここではMerkle Proofの特殊な性質を利用する。Tornadoのすべての入金記録は、オンチェーンに構築されたMerkle Treeの最下層の葉(リーフノード)として保存されており、葉の総数は約2の20乗(100万以上)であり、そのほとんどは空の状態(初期値が設定されている)である。新しい入金が発生するたびに、対応する特徴値「コミットメント(Commitment)」が葉に書き込まれ、Merkle Treeのルートが更新される。

たとえば、Bobの入金がTornado史上1万回目のものであれば、その入金に関連する特徴値CnがMerkle Treeの1万番目の葉に書き込まれる。つまりC10000 = Cnとなる。その後、コントラクトが自動的に新しいルートを計算し、更新する。(補足:計算負荷を減らすため、Tornadoコントラクトは直近の変更があったノードデータをキャッシュしている。下図のFs1、Fs2、Fs0などが該当する)

Merkle Proof自体は非常に簡潔で軽量である。これはツリー構造の検索・トレーサビリティにおける簡潔性を利用している。ある取引TDがMerkle Tree内にあることを外部に証明するには、ルートに対応するMerkle Proof(下図右側)を提示すればよい。この証明は非常に簡潔である。仮にMerkle Treeが非常に大きく、葉が2の20乗個(約100万件の入金記録)あったとしても、Merkle Proofはたった21個のノード情報で構成される。

ある取引H3がMerkle Treeに含まれていることを証明するには、H3とTree上の他の部分データを使ってルートを再生成できることを示せばよい。このルート生成に必要なデータ(Tdを含む)がMerkle Proofを構成する。
Bobが出金する際には、自分が持つ証明書がMerkle Tree上の何らかの入金ハッシュCnに対応していることを証明しなければならない。つまり、以下の2点を証明する必要がある:
・CnがオンチェーンのTornadoコントラクト内のMerkle Treeに存在すること。具体的には、Cnを含むMerkle Proofを構築すればよい。
・CnがBobが持つ入金証明書と関連していること。

Tornadoのビジネスロジック詳細解説
Tornadoのユーザーインターフェースのフロントエンドコードにはあらかじめ多くの機能が実装されている。ユーザーがTornado Cashのウェブページを開き「入金」ボタンをクリックすると、付随するプログラムがローカルで2つの乱数Kとrを生成し、Cn = Hash(K, r) を計算して、このCn(下図のcommitment)をTornadoコントラクトに送信し、Merkle Treeに挿入する。つまり、Kとrは秘密鍵のようなものであり、非常に重要である。システムはユーザーにこれらを安全に保管するよう促す。後に出金時にも再利用が必要になる。

(ここで言うencryptedNoteはオプションで、ユーザーが証明書Kとrを自分の秘密鍵で暗号化し、オンチェーンに保存できる。忘失防止のため)
注意すべきは、以上の処理はすべてオフチェーンで行われる点である。つまり、Tornadoコントラクトや外部の観察者にはKとrは一切知られない。Kやrが漏洩すれば、それはウォレットの秘密鍵が盗まれたのと同じである。

Tornadoコントラクトがユーザーの入金を受け取り、Cn = Hash(K, r) を受信すると、これをMerkle Treeの最下層に新しい葉として挿入し、ルートの値を更新する。したがって、Cnとユーザーの入金行動は一対一に対応しており、外部からは各Cnがどのユーザーに対応するか、誰がミキサーにトークンを預けたか、各預入者の入金記録Cnが何かを知ることができる。
出金ステップでは、出金者がフロントエンド画面に証明書(入金時に生成された乱数Kとr)を入力すると、Tornado CashのフロントエンドプログラムがKとr、Cn = Hash(K, r)、およびCnに対応するMerkle Proofを入力パラメータとしてZK Proofを生成する。これにより、「CnがMerkle Tree上に存在する入金記録であること」と「KとrがCnに対応する証明書であること」が証明される。
このステップは、「私はMerkle Tree上に記録された何らかの入金記録に対応する秘密鍵を知っている」ことを証明することに相当する。ZK ProofがTornadoコントラクトに提出された際、上記4つのパラメータはすべて隠蔽されており、外部(Tornadoコントラクトを含む)には知られないため、プライバシーが保たれる。
ZKProof生成に含まれる他のパラメータとしては、出金時のMerkle Treeのルートroot、任意の受取アドレスA、リプレイ攻撃防止の識別子nf(後述)がある。この3つのパラメータはオンチェーンに公開され、外部から確認可能だが、プライバシーには影響しない。

ここで一つの細部がある:Cn生成時には、1つの乱数ではなく2つの乱数Kとrを使用している。これは単一の乱数では安全性が不十分で、衝突の可能性があるためである。例えば、単一乱数の場合、異なる2人の入金者が偶然同じ乱数を使い、Cnが重複してしまう可能性がある。
図中のAは、出金を受け取るアドレスを指し、出金者が自分で入力する。nfはリプレイ攻撃を防ぐための識別子であり、その値はnf = Hash(K) となる。ここでKは、Cn生成時に使われた2つの乱数のうちの1つ(Kとr)である。これにより、nfはCnと関連付けられ、つまり各Cnには対応するnfが一意に存在する。
なぜリプレイ攻撃を防ぐ必要があるのか? ミキサーの設計特性上、出金時にユーザーが引き出したコインがMerkle Treeのどの葉Cnに対応するかは不明であり、出金者とどの入金者に関連するかも分からない。したがって、出金者が何回入金したかも分からない。この性質を利用して、出金者は繰り返し出金を試み、リプレイ攻撃を行い、資金プールを完全に枯渇させてしまう可能性がある。

nf識別子の役割は、各イーサリアムアドレスが持つトランザクションカウンターnonceに似ており、どちらもトランザクションのリプレイを防ぐために設けられている。出金が発生する際、出金者はnfを提出し、それがすでに使用済みかどうかをチェックする。使用済みであれば出金は無効。未使用であれば出金は有効とされ、対応するnfが記録される。次に誰かが同じnfを提出しても、出金は即座に無効と判定される。

もし誰かがコントラクトに記録されていない適当なnfを生成したらどうなるか? それは不可能である。なぜなら、出金者がZK Proofを生成する際にnf = Hash(K) を満たす必要があり、乱数Kは入金記録Cnに関連している。つまり、nfは記録された入金Cnに関連している。適当にnfを偽造しても、既存の入金記録と一致しないため、有効なZK Proofを生成できず、以降の処理は失敗し、出金も成功しない。
また疑問を持つ人もいるかもしれない:nfは不要ではないか? 出金時にZK証明を提出して自分が何らかのCnに関連していることを証明するのだから、出金時にそのZK Proofが既にオンチェーンに提出されていないかを確認すればよいのではないか?
しかし実際には、そのコストは非常に高い。なぜならTornado Cashコントラクトは過去のZK Proofを永久に保存しない。これはストレージの浪費になるためである。新たに提出されたZK Proofと過去のすべてのProofを比較するよりも、非常に小さなサイズの識別子nfを永久保存する方がはるかに効率的である。
出金関数のコード例に基づくと、必要なパラメータとビジネスロジックは以下の通り:
ユーザーはZKProof、nf(NullifierHash)= Hash(K)、任意の出金受取アドレスrecipientを提出する。ZKProofはCnおよびK、rの値を隠蔽し、外部がユーザーの身元を特定できないようにする。recipientには通常、クリーンな新規アドレスが指定され、個人情報を漏らさない。

ただし、もう一つの問題がある。出金時にユーザーは追跡されないよう、新しく作成したアドレスから出金トランザクションを送信することが多いが、この新アドレスにはETHがなく、ガス代を支払えない。そのため、出金アドレスが取引を送信する際には、中継者(relayer)を明示的に宣言し、relayerがガス代を立て替える。その後、ミキサーコントラクトがユーザーの出金額から一部を差し引き、relayerに報酬として支払う。

以上より、TornadoCashは出金者と入金者の関連性を隠蔽でき、ユーザー数が多い状況ではまるで人ごみに紛れた犯人を警察が追跡できないように機能する。出金プロセスにはZK-SNARKが不可欠であり、隠蔽されるwitness部分には出金者の重要な情報が含まれており、これがミキサーの最も重要なポイントである。現時点では、TornadoはZKに関連するアプリケーション層において最も巧妙なプロジェクトの一つと言えるだろう。
TechFlow公式コミュニティへようこそ
Telegram購読グループ:https://t.me/TechFlowDaily
Twitter公式アカウント:https://x.com/TechFlowPost
Twitter英語アカウント:https://x.com/BlockFlow_News














