#3:KyberSwap事件:極めて巧妙な計算による丸め誤差の巧みな悪用

KyberSwap事件:極めて巧妙な計算による、丸め誤差の巧みな悪用

#3:KyberSwap事件:極めて巧妙な計算による丸め誤差の巧みな悪用

2023年11月23日、KyberSwapを標的とした一連の攻撃が発生しました。これらの攻撃により、総額4,800万ドル以上の損失が発生しました。根本的な問題は、KyberSwapの再投資プロセスにおける丸め方向の誤りから生じました。これが、不適切なティック計算、ひいては流動性の二重計上につながりました。

この事件の詳細については、"精度損失の悲劇再び:KyberSwapインシデントの徹底分析" という包括的なレポートを公開しました。より深い理解のために、完全な分析を参照することをお勧めします。以下に、このインシデントの簡潔な紹介を提供し、2023年のトップ10セキュリティインシデントの1つとして強調します。

背景

KyberSwapは、分散型自動マーケットメーカー(CLAMM)プラットフォームです。集中流動性の市場需要を満たすため、Uniswap V3をベースに、再投資カーブなどいくつかの改良を施したKyberSwap Elasticがローンチされ、流動性提供の利回りの自動複利計算を可能にしています。

1. ティックと平方根価格

Uniswap V3ライクなCLAMMにおける「ティック」は、価格を離散的にマークするために使用され、LPは全範囲ではなく固定範囲で流動性を提供できます(「集中」という言葉の由来)。

流動性は、任意の2つのティック(隣接している必要はない)の間の範囲に配置できます。つまり、ティックインデックスのペア(下限ティックと上限ティック)です。具体的には、各ティック(整数インデックスi)の価格は次のように定義されます。

実際には、平方根価格sqrtPまたはsqrtPriceと表記)が使用されます。

現在の平方根価格から現在のティックを計算することも可能です。

明らかに、特定のティックに対して計算される平方根価格は1つだけですが、複数の平方根価格が同じティックを指し示す可能性があります。詳細については、Uniswap V3およびKyberSwapのドキュメントを参照してください。

2. 再投資カーブ

Uniswap V3ベースのCLAMMは、LP手数料のプール利用率と、再投資に必要な多額のガス料金という問題を抱えています。そのため、KyberSwapは問題を解決するために再投資カーブを採用しました。

再投資カーブの鍵は、各スワップで収集された手数料が、無限範囲内の「再投資流動性」としてプールに追加の流動性として蓄積されることです。再投資トークンはLPにミントされ、蓄積された再投資流動性はLPに適切に割り当てられます。さらに、再投資流動性もスワップおよび価格計算プロセスに参加します。

上記で紹介した計算に対応するコードは、以下のコードスニペットのcomputeSwapStep関数に示されています。これは、対応するプールのものです。

再投資流動性のため、この関数におけるliquidityは2つのコンポーネントの合計であることに注意してください。ベース流動性用のbaseLと、蓄積された再投資流動性用のreinvestLです。

3. KyberSwapでのスワップ

前述のKyberSwapのプールにおけるswap関数の実装は、以下の図のように抽象化できます。

ティック計算に関わる重要なロジックは、スワップのwhileループ内にあり、青い四角で強調されています。具体的には、主要なロジックはcomputeSwapStep関数と_updateLiquidityAndCrossTick関数に関連しています。前者は、指定されたスワップの入力および出力金額やnextSqrtPなどの主要な状態を計算し、後者はティックをまたぐケースを処理します。

伝統的に、価格が上昇した場合、ティックを右/上にシフトすると言います。そうでなければ、ティックが左/下に移動すると言います。

後述する脆弱性をよりよく理解するために、以下の図に示すように、computeSwapStep関数の関連コードロジックを調べることは不可欠です。

具体的には、calcReachAmount関数は、ターゲット価格targetSqrtPに必要な入力トークンを計算します(50-57行目)。usedAmountspecifiedAmountより大きい場合、ティックはまたがず、nextSqrtPdeltaL(デルタ流動性)から計算されます(59-62行目)。deltaLestimateIncrementalLiquidity関数を使用して決定され、最終価格nextSqrtPcalcFinalPrice関数で計算されます(70-79行目)。必要な入力が少ない場合、nextSqrtPは次のティックの価格に設定されますが、このケースは攻撃では使用されません。

上記の手順から、ティックがまたがっていない場合、computeSwapStepによって返されるnextSqrtPは次のティックのsqrtPより大きくならないはずであることが明らかです。しかし、価格が流動性(ベース流動性およびデルタ流動性)と精度損失に依存しているため、攻撃者はティックがまたいでいないにもかかわらず、nextSqrtPを大きく操作することができました。

脆弱性分析

根本原因は、SwapMathコントラクト(computeSwapStep関数によって呼び出される)のデルタ流動性計算(つまり、estimateIncrementalLiquidity関数)における丸め方向の誤りによる、不完全なティック計算にあります。これが、後続のティック計算に不適切に影響を与えます。

興味深いことに、188行目(青い四角で強調)のコメントを見ると、deltaLnextSqrtPを丸めて下げるために上方に丸められる意図があったことがわかります。しかし、189行目のmulDivFloor関数の使用により、deltaLは誤って下方に丸められました。その結果、nextSqrtPは不正確に上方に丸められました。

攻撃分析

攻撃者は複数の攻撃トランザクションを開始し、各トランザクションで複数のプールを不正に引き出しました。簡単にするために、以下の議論は、攻撃トランザクション内の最初の攻撃に基づいています。

コア攻撃ロジックは、以下の6つのステップで構成されています。

  1. AAVEからフラッシュローンで2,000 WETHを借り入れます。

  2. 犠牲プール0xfd7bで6.850 WETHを6.371 frxETHにスワップします。このステップは、現在流動性がない場所に現在のティックとcurrentSqrtPをプッシュするために使用されます。

    • currentSqrtPは攻撃者によってランダムに選択されたように見え、スワップはこの価格で正確に停止します。
    • このステップの後、ベース流動性(baseL)はゼロになりますが、再投資流動性(reinvestL)はゼロではありません。
  3. プールに流動性を追加し、その後一部を削除します。このステップは、範囲と総流動性を希望の量に制御するために使用されます。

    • ティック範囲はcurrentSqrtPに基づいて選択されます。
    • 攻撃に必要な流動性はティック範囲から派生させることができますが、対応する計算ロジックはさらに調査が必要です。
  4. プールで387.170 WETHを0.06 frxETHにスワップします。このステップは、nextTick == currentTick となるように現在のティックを操作するために使用されます。具体的には、ステップ4のスワップは、ティック111,310がまたがれていないとプールに誤認させます。しかし、実際には、currentSqrtPはティック111,310のsqrtPより大きくなっています

  5. プールで0.06 frxETHを396.244 WETHにスワップします。前のステップと比較してスワップ方向が逆になっていることに注意してください。このステップでは、スワップを収益化し、結果としてプールから不正に引き出すために、流動性が二重計上されます。

  6. フラッシュローンを返済し、6.364 WETHと1.117 frxETHを回収します。

詳細については、計算と図を含む包括的な分析を参照してください。

まとめ

このインシデントの根本的な問題は、KyberSwapの再投資プロセスにおける丸め方向の誤りに起因し、不正確なティック計算、ひいては流動性の二重計上につながりました。このインシデントは、DeFiプロトコルにおける精度損失問題の複雑で捉えどころのない性質を浮き彫りにし、コミュニティ全体に深刻な課題を突きつけています。

2023年のこの攻撃は、その複雑さ、異常に微妙な計算を特徴とし、コミュニティに大きな試練を与えた多くの精度関連のセキュリティインシデントの典型例として際立っています。さらに、当局との広範な交渉の後、攻撃者はプロトコルの完全な制御を要求するという挑発的なトーンのメッセージを公に発表しました。

このシリーズの他の記事を読む:

Sign up for the latest updates