盲信は死を招く:Web3ウォレットにおける「トランザクション・シミュレーション」の強制力
現場でインシデント対応をしていると、決まって耳にする悲痛な叫びがある。「何もしていないのに、なぜかウォレットが空になった」。
エンジニアなら知っているはずだ。ブロックチェーンの世界に「何もしない」という事象は存在しない。彼らは、巧妙に難読化された setApprovalForAll や、見栄えの良いDAppの裏で実行される悪意あるコントラクト関数を、自らの手で「署名(承認)」してしまったのだ。
かつてSCADAの現場で、PLCのレジスタを直接操作する不正なModbusパケットが、HMI(監視画面)の表示を偽装してオペレーターを騙した事例があった。Web3におけるトランザクション署名も全く同じだ。ユーザーに見えている画面(UI)と、実際にチェーン上で実行されるロジック(バイトコード)の乖離こそが、攻撃者が突く最大の盲点である。
今日は、開発者がユーザーを守るための最後の防波堤、「トランザクション・シミュレーション」の実装について、泥臭い知見を共有する。
—
1. なぜ「署名」が最大の脆弱性なのか
攻撃者は、transferFrom や permit といった権限委譲関数を、まるで「NFTの受け取り」や「エアドロップの承認」のように見せかける。
攻撃のPoC(概念):
ユーザーが「Claim Free NFT」ボタンを押すと、バックエンドが permit メソッドを呼び出す署名リクエストを投げる。ユーザーは単なる「ログイン」だと思って eth_sign を押す。その瞬間、攻撃者はユーザーの全トークンを奪取する権限を手に入れる。
これを防ぐには、「署名する前に、そのTX(トランザクション)が実行された後の状態変化を、ユーザーが理解できる言語で提示する」しかない。
—
2. 実践:シミュレーション・エンジンの構築(JavaScript/ethers.js)
シミュレーションの肝は、実際にチェーンに送る前に、eth_call や各チェーンプロバイダー(AlchemyやTenderlyなど)の「Simulation API」を叩くことだ。以下に、署名前にトランザクションの結果を予測し、リスクを判定する実用的なコードを示す。
/**
* 署名前にトランザクションをシミュレートする関数
* @param {Object} txRequest - 送信予定のトランザクションオブジェクト
* @param {Object} provider - ethers.jsのプロバイダー
*/
async function simulateTransaction(txRequest, provider) {
try {
// 1. eth_callを使用して、実際のトランザクション実行をシミュレート
// 成功すれば revert されずに結果が返る。失敗すれば例外が発生する。
const result = await provider.call(txRequest);
console.log("シミュレーション結果:", result);
// 2. ここで重要:結果が「資産の移動」を伴うか解析する
// 実際の実務では Tenderly Simulation API などを使い、
// 差分(Balance Changes)をJSONで受け取り解析することを強く推奨する
return { safe: true, message: "予期せぬ資産移動は検知されませんでした。" };
} catch (error) {
// 3. 実行失敗=コントラクト側で何らかのブロック処理がある可能性
console.error("シミュレーション失敗:", error);
return { safe: false, message: "このトランザクションは実行時にエラーが発生します。悪意あるコードの可能性があります。" };
}
}
// 実際の呼び出し例
const tx = {
to: "0x...",
data: "0x...", // ここに難読化された呼び出しデータが入る
from: "0x..."
};
const analysis = await simulateTransaction(tx, provider);
if (!analysis.safe) {
alert("警告: " + analysis.message);
throw new Error("署名を中止しました");
}
—
3. フロントエンド開発者が守るべき「設計の鉄則」
コードを書くとき、以下の3点を意識するだけでインシデント確率は劇的に下がる。
1. 「何が起きるか」を人間語に変換する:
data フィールドのバイトコードをそのまま表示してはならない。abi.decode を用いて、関数名と引数を読み取り、「あなたは 100 USDC を 0x… に送ろうとしています」とUIに明記せよ。
2. 危険な関数をホワイトリストで管理する:
setApprovalForAll や approve が呼ばれる場合は、UI上で「強い警告色(赤)」を表示し、ユーザーに再度の確認(チェックボックスなど)を要求するフローを強制すること。
3. シミュレーションAPIの活用:
自前で eth_call を書くのも良いが、TenderlyやBlocknativeが提供するSimulation APIは、トークンの価格変動やNFTの移動まで可視化してくれる。これを使わない手はない。
—
4. 最後に:セキュリティは「疑うこと」から始まる
Web3のUI開発において、エンジニアは「ユーザーは画面上の情報を読み取らない」という前提で設計しなければならない。
今回のシミュレーション実装は、いわば「車が壁に衝突する前に、AIが衝突を予知してブレーキをかけるシステム」だ。しかし、ブレーキをかけても、アクセルを踏み続けるユーザーは必ずいる。
だからこそ、我々エンジニアは、不審なTXを検知した瞬間に、UIを強制的にロックし、ユーザーの資産を守る「強制力」を持たせる必要がある。
「便利さ」と「安全性」のバランスを追求するのは開発者の腕の見せ所だが、セキュリティに関しては「安全側に倒しすぎる」ことはない。 署名ボタンを押させる前に、もう一度だけユーザーに考えさせる時間を。それが、君が守れる最大の防波堤だ。
コメント