こんにちは!IoT・OTの現場や、最先端のWeb3セキュリティの世界へようこそ。
今回は、ブロックチェーンの世界でいま大きな話題になっている「DAO(分散型自律組織)ガバナンスにおけるフラッシュローンを用いた投票権買収」という、ちょっとスリリングだけどとっても重要なテーマについてお話ししていきますね。
「なんだか難しそうな名前だな…」って身構えちゃいましたか? 大丈夫です! 家の鍵の防犯や、町内会の決議の仕組みに例えながら、一歩ずつ優しく紐解いていきましょう。
—
1. 家の鍵と町内会で考えてみる:フラッシュローン攻撃ってなに?
まずは、ブロックチェーンの世界を「私たちの住むマンション」に例えてみましょう。
このマンションには、住民全員が参加して「来年のペンキの色は何色にするか」「管理人さんを新しく雇うか」といった大事なことを決める「町内会(DAO)」があります。
ルールとして、「持っているマンションの株券(ガバナンストークン)の数だけ、投票の力が強くなる(1株=1票)」という仕組みになっています。
ここで、ある困った泥棒(攻撃者)が現れました。この泥棒、実は手持ちの現金はほとんどありません。
しかし、マンションのすぐ近くにある「超スピードで大金を一時的に貸してくれる質屋さん(フラッシュローン)」を見つけました。
フラッシュローンというのは、「同じブロック(ほんの一瞬の時間)の中で借りて、同じ時間の中に全額返済するなら、担保なしで何億円でも貸してあげるよ」という、現実世界には絶対にないブロックチェーン特融の魔法のような仕組みです。
泥棒はこの仕組みを使って、以下のような悪巧みを実行します。
1. 一瞬の借金: 質屋さんから10億円分のマンションの株券を、数秒の間だけ借りる。
2. 強引な投票: 大量の株券を使って、「マンションの金庫にあるお金を全部、泥棒の口座に振り込む」という悪意ある提案に「賛成!」と一票を投じる。株券の数が圧倒的なので、提案はあっさり可決されてしまいます。
3. 証拠隠滅: 提案が可決された直後、借りた10億円分の株券をすぐ質屋さんに全額お返しする。
泥棒の財布には、最初から1円も残っていません。しかし、ほんの数秒間だけ「大株主」になりすましたことで、マンションの財産を持ち逃げすることに成功してしまったのです。これが、フラッシュローンを用いた投票権買収の恐ろしい手口です。
—
2. 攻撃を防ぐための「防犯の知恵」
こんなずるい手口を使われたら、マンションの住民はたまったもんじゃないですよね。
では、スマートコントラクト(自動契約プログラム)の世界では、どうやってこの泥棒を防げば良いのでしょうか?
セキュリティの現場では、主に2つの強力な「防犯カメラと頑丈なドア」が使われます。それが 「スナップショット投票(過去の記録)」 と 「投票遅延メカニズム(タイムロック)」 です。
防御策①:スナップショット投票(「昨日のあなた」を信じる)
フラッシュローンがなぜ強力かというと、「今この瞬間に株券をかき集めてすぐ投票できるから」ですよね。
それなら、「投票できるのは、昨日(または先週)の時点で株券を持っていた人だけ!」というルールにしてしまえばいいのです。
これをプログラムの世界では、ブロック番号やタイムスタンプを記録して、過去の残高を参照する仕組み(Snapshotなど)で実現します。
これなら、泥棒が今日急いで10億円借りてきても、「いやいや、あなたは昨日株券を持っていなかったから、今回の投票権はゼロ票です」とピシャリと追い返すことができます。
防御策②:投票遅延メカニズム(「今すぐ決めるな」の時間差攻撃)
もう一つの対策は、「提案が出てから実際に可決・実行されるまでに、数日間のタイムラグ(猶予期間)を設ける」という方法です。
もし泥棒がフラッシュローンで強引に提案を通したとしても、実行されるまでに3日間の猶予があればどうなるでしょう?
まともな住民たちが「あれ? この提案、おかしくないか?」と気づいて、警察(マルチシグ管理者や他の正当なホルダー)に通報したり、緊急対策を打ったりする時間が十分に生まれますよね。
—
3. 実装コードで見てみよう:脆弱なコントラクトと安全なコントラクト
百聞は一見にしかず。実際に開発者が書くスマートコントラクト(Solidity)のコードを見てみましょう。
まずは、「フラッシュローンに完全無防備な、危なっかしいコード」です。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
}
contract VulnerableDAO {
IERC20 public governanceToken;
mapping(uint256 => uint256) public proposalVotes; // 提案IDごとの賛成票
constructor(address _token) {
governanceToken = IERC20(_token);
}
// 【危険】投票した瞬間の残高だけでカウントしちゃっている!
function vote(uint256 proposalId) external {
// 今この瞬間のトークン残高をそのまま投票力にしちゃっています
uint256 votingPower = governanceToken.balanceOf(msg.sender);
// フラッシュローンで借りたばかりの大量トークンでも通ってしまう
proposalVotes[proposalId] += votingPower;
}
}
上記のコードでは、balanceOf(msg.sender) をその場で呼び出しているため、フラッシュローンで一時的にトークンをかき集めた攻撃者に簡単に突破されてしまいます。
それでは次に、「スナップショット(過去のブロック時点の残高)を利用した安全なコード」を見てみましょう。一歩ずつ対策を学んでいきましょうね!
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGovernanceToken {
// 過去の特定のブロック時点における残高を調べる関数
function getPastVotes(address account, uint256 blockNumber) external view returns (uint256);
}
contract SecureDAO {
IGovernanceToken public governanceToken;
mapping(uint256 => uint256) public proposalVotes;
// 提案ごとに「いつの時点の記録を基準にするか」のブロック番号を保存
mapping(uint256 => uint256) public proposalSnapshotBlock;
constructor(address _token) {
governanceToken = IGovernanceToken(_token);
}
// 新しい提案を作る時に、スナップショット(基準となるブロック)を記録する
function createProposal(uint256 proposalId) external {
// 現在のブロック番号を記録(この時点より後に手に入れたトークンは投票に使えない)
proposalSnapshotBlock[proposalId] = block.number;
}
// 【安全】過去のブロック時点の残高を参照する
function vote(uint256 proposalId) external {
uint256 snapshotBlock = proposalSnapshotBlock[proposalId];
// 提案が作成された時点(過去)の保有量を取得する
// これにより、今フラッシュローンで借りてきたトークンは無視される!
uint256 votingPower = governanceToken.getPastVotes(msg.sender, snapshotBlock);
proposalVotes[proposalId] += votingPower;
}
}
このように、getPastVotes のような過去のブロック記録を参照する仕組み(OpenZeppelinの ERC20Votes などで標準実装されています)を使うだけで、フラッシュローンによる買い占め攻撃を綺麗に無力化することができるのです。
—
4. おわりに:現場のエンジニアとして大切なこと
DAOのガバナンス設計やスマートコントラクトの開発は、まるで「誰もが自由に出入りできるデジタル都市の法律」を作るようなものです。一度デプロイしてしまったら、悪意ある攻撃者はシステムの隙をほんのコンマ数秒の隙間も見逃さずについてきます。
「動くだけのコード」を書く段階から一歩進んで、「この変数、今の値を見て本当に大丈夫か?」「過去のデータや時間差を考慮する必要はないか?」という防衛意識(セキュリティ・マインドセット)を持つことが、私たちエンジニアの何よりの武器になります。
日々の開発やインフラ構築の中で、泥臭く、しかし確実なセキュリティ対策を積み重ねていきましょう!それではまた次回の記事でお会いしましょう。
コメント