Develop with pleasure!

福岡でCloudとかBlockchainとか。

Zcash Orchardプールの脆弱性

先日発見されたZcashの脆弱性について↓

zfnd.org

Zcash Orchardプールのバグ

脆弱性の原因は、Orchardプールのzk証明回路(Action circuit)の制約不足によるもので、これにより無効な状態遷移が可能になりシールドプール内での二重使用のリスクが存在した。

プール

OrchardというのはZcashの3世代めのシールドプール。プールというのは秘匿された金額と状態の集合で、Bitcoinで言うUTXOセットのようなもの。現在のZcashの秘匿コインのシールドプールは、以下の3種類が存在する。

  • Sprout(2016年):最初のシールドプールで、libsnark系の証明システムを採用しBN254曲線上で動作。Trusted Setupを必要とし、シールドトランザクションの生成に3GBのRAMを必要とし生成に数十秒かかる。※ちなみにこのSproutの実装にも偽造脆弱性(CVE-2019-7167)が発見されており、この種のバグは今回が初めてではない。
  • Sapling(2018年):Groth16ベースの証明システムを採用し、BLS12-381曲線に刷新。回路内の楕円曲線演算には埋め込み曲線 Jubjubを使い、コミットメントは効率の良いPedersen系へ移行。これによりプルーフの生成が数秒・低メモリになり、モバイルでのシールド送金が現実的に。
  • Orchard(2022年):今回のバグが発見されたプールで、証明システムをHalo 2を採用し、Pasta曲線を採用しTrusted Setupを排除。コミットメントおよびハッシュには楕円曲線ベースで回路内でのコストが低いSinsemillaを採用。

各プールのコインは、新世代のプールへの移行が推奨されてきた(特にSproutは偽造バグがあったため)。ただ、新世代のプールが利用可能になっても、古いプールにもコインは残っており、コンセンサスルール上、今も3つのプールが並行して有効なまま。Bitcoinで言うと、Segwitが導入されてもレガシーなP2PKH/P2SHにコインが残っているようなもの。

なお、各プールはそれぞれ独立したノート形式・コミットメントツリー・nullifierセット・回路を持つ。Bitcoinなどでは透過的な世界でScriptと明示的な金額のチェックが担っていた検証を、シールドプールではまるごと回路(とそれを検証する証明システム)が肩代わりしている。検証者には金額もノート本体も見えず、見えるのは回路の検証鍵に対してそのプルーフが通るかどうかだけ。つまり回路はそのプールのコンセンサスルールそのもので、そのため今回の制約不足は致命的な影響があった。

今回の脆弱性

今回の脆弱性は、OrchardのHalo 2の回路の実装の問題。正確には、Halo 2の証明システム本体ではなく、回路実装をまとめた halo2_gadgets crateのecc::chip::mul(楕円曲線のスカラー乗算ガジェット)のなかにあった。本来弾くべき無効な入力が検証を通ってしまう種類のバグで、制約不足の典型例にあたる。

Zcashの開示によると、根本原因はecc::chip::mulで行われる楕円曲線上の点のバイナリ法を用いたスカラー倍算のチェック処理。バイナリ法は、ループ処理でスカラー値のビットを1 bitずつ見ながら、途中結果を2倍し、bitが立っていればそれを加算するという計算を繰り返す処理(詳細は以前の記事参照)。

問題となったのは、計算を始める際の基点となる点の固定処理。上記のバイナリ法ベースでざっくり言うと、スカラー値を5とした場合、5Gを計算するところ、ループ内で足し込む点が本当に基点Gであるという固定がなく、Gの代わりに別の点Hを使った5Hを計算してもパスしてしまう、というもの。このHが攻撃者の自由に選べる任意の点であるのが攻撃可能なポイント。

もう少し具体的には、Zcashの回路は制約の数を削減してコストを下げるために、バイナリ法のループの最初・最後と、ループ内の処理とで内容を切り替えるハイブリッドな構成をとっている。本来は各区間が参照する点を入力の基点Gに固定する必要があるが、このうちループ内区間の固定が欠けていた。

結果として、証明者は基点とは異なる自由な定数Hに対してループを走らせることができ、ガジェットの出力が本来のスカラー・Gではなく、a・G+b・Hになり得る、つまり「x倍した」と称しながら、まったく別の点を混ぜ込めることができた。

これがOrchardの回路の値の検証(入力=出力+feeなど)の保証を崩し、攻撃者が偽の値で正当に見えるプルーフを生成できる状態となった。

総供給量のインフレーションは防止

ただし、この回路バグだけではZECの総供給量のインフレーションは起こせない。Zcashにはturnstileという仕組みがあって、各プール(Sprout / Sapling / Orchard / transparent*1 / lockbox*2)の境界で「これまでにそのプールへ入った価値 − 出た価値」を追跡し、プールから外へ出せる総量に上限をかけている。

そのためOrchardプール内部では偽造・二重使用の可能性はあるけど、プールから外へ持ち出せる総量(上限)は崩せない。

ただ、直近だとシールドされているコインの各プールの内訳は、

  • Orchard:約450万ZEC(シールド分の約88%)
  • Sapling:約59.2万ZEC(約12%)
  • Sprout:約2.5万ZEC(0.5%未満)

なので、影響は大きい。

対応

対応は2段階で行われた模様。直接パッチを最初に公開すると、更新後のコードから欠陥の性質が露見し、修正完了前に悪用される恐れがあるため、まずOrchardを止める→落ち着いてから回路を直すという順序が取られた。

悪用の可能性は?

バグ自体は修正されたけど、現状mainnetでの悪用の証拠はない一方、悪用が無かったことを暗号学的に証明する手段も存在しないとされている。過去のトランザクションについて修正後の回路でリプレイすれば、新しい制約に違反するtxがあるかは判定でき、見つかれば悪用は確定すると思ってるんだけど、暗号的に証明できないとされている理由がよく分かっていない。

turnstileによって供給量の上限は保たれるものの、シールド供給のほとんどを占めるOrchardプールにおける問題というのは重そう。強力なプライバシーと供給の検証可能性が原理的に両立しにくいので難しいよね。でもこういう事件を経て、プールに偽造が存在しないことを誰もが暗号的に検証できる会計機構を組み込むようなアプローチが進んでいくんだろう。

*1:シールドされていない=秘匿されていないコインのプール

*2:2024年に導入された特殊なプールで、開発資金を一時的に保持しておくプロトコルレベルのプール

⚡ Zap me!

Lightning QR

Lightning Address

techmedia_think@walletofsatoshi.com