玄関の鍵をコピーされたら?「Permit」関数と署名再利用の危険な関係
みなさん、こんにちは。IoTデバイスのファームウェア解析や、ブロックチェーンのスマートコントラクトを日々「解剖」しているセキュリティリサーチャーです。
今日は、スマートコントラクト界隈で非常に便利な機能として注目されている「EIP-2612(Permit)」についてお話しします。これ、一言で言うと「銀行でいちいちサインを書かなくても、事前に許可証を渡しておけば手続きが進められる」という魔法のような機能なんです。
しかし、この「許可証」の扱いを一歩間違えると、泥棒に鍵を複製されるような事態を招いてしまいます。今回は、新人開発者の方でもわかるように、身近な防犯に例えて解説していきますね。
—
1. Permitって何?:家の合鍵を渡す仕組み
従来のERC-20トークンで送金するには、一度approve(承認)関数を呼び出し、次にtransferFromを呼び出すという「2ステップ」が必要でした。これが面倒なので、「事前にサイン入りの許可証(Permit)を書いておけば、あとは誰かが勝手に送金を実行してくれる」という仕組みが生まれました。
これを「家の鍵」に例えてみましょう。
- 従来の仕組み:業者が来るたびに、あなたが毎回玄関まで行って鍵を開けてあげる。
- Permitの仕組み:事前に「この業者に、今日だけこの部屋に入っていいよ」という署名入りメモ(Permit)を渡しておく。業者はそのメモを見せるだけで、あなたが不在でも作業ができる。
便利ですよね? しかし、ここに一つだけ「絶対に守らなければならないルール」があります。それが「一度使ったメモは二度と使えないようにすること」です。
—
2. 攻撃者の狙い:署名の「使い回し」と「先回り」
もし、あなたが渡したメモを、悪意のある誰かがコピーして、別のタイミングで何度も使おうとしたらどうなるでしょう?
署名の再利用(リプレイ攻撃)
「100トークン送っていいよ」という署名入りメモを、攻撃者が横取りして、1回と言わず10回も送信したら……あなたの財布は空っぽです。これを防ぐための仕組みが nonce(ナンス)という「通し番号」です。
先回り攻撃(フロントランニング)
もう一つの脅威が「フロントランニング」です。あなたが正規の業者にメモを渡したとします。それをネットワーク上で盗み見した攻撃者が、「手数料(ガス代)を多めに払うから、俺の処理を先に処理してくれ!」とマイナーに頼み込み、あなたのメモを先に横取りして使ってしまうのです。
—
3. 防御の鉄則:nonceを管理しよう
この攻撃を防ぐには、コントラクト側で「この番号のメモはもう使ったよ!」と記録しておく必要があります。
以下は、Permit機能を実装する際の安全な考え方を示すサンプルコードです。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SecurePermit {
// ユーザーごとのnonceを記録するマップ
// 鍵の「通し番号」を管理して、二重利用を防ぎます
mapping(address => uint256) public nonces;
// Permit関数の簡易イメージ
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v, bytes32 r, bytes32 s
) external {
// 1. 有効期限チェック
require(block.timestamp <= deadline, "許可証の期限が切れています!");
// 2. 署名から署名者(owner)を復元する処理
// ... (署名検証のロジックがここに入ります) ...
// 3. nonceをインクリメント(使い終わった鍵番号を更新)
// これにより、同じ署名データは二度と使えなくなります
nonces[owner]++;
// ... 承認処理を実行 ...
}
}
ポイントはここ!
nonces[owner]++: この一行が命綱です。メモを使うたびにこの番号を更新することで、古いメモを再利用しようとしても、コントラクト側が「その番号の鍵はもう捨てたよ」と拒絶できるようになります。deadline: 「今日中だけ有効」という制限です。もし何らかの理由で署名が流出しても、時間が過ぎれば無効化されるため、被害を最小限に抑えられます。
—
4. セキュリティ担当者からのアドバイス
新人開発者の皆さんが明日から意識すべきことは、以下の3点です。
1. 「署名」は「パスワード」と同じ:署名データを送る際は、必ずTLS/SSL(HTTPS)などのセキュアな通信経路を使用してください。
2. nonceの管理を信じるな:自分のコントラクトにnonceが正しく実装されているか、テストネットで徹底的に「同じ署名で2回送る」というテスト(リプレイテスト)を必ず行ってください。
3. ガス代設定を甘く見ない:フロントランニング対策として、重要な操作を行うときはガス代を調整したり、チェーンの特性を理解して設計することが重要です。
—
まとめ:一歩ずつ安全な世界へ
Permit関数は、ユーザー体験を劇的に向上させる素晴らしい技術です。しかし、その裏側にある「nonce」という小さな通し番号の管理を怠ると、一瞬で資産が失われるリスクもあります。
「鍵を渡すときは、必ず番号を振る」。
この単純なルールを徹底するだけで、あなたのコントラクトは格段に強固になります。
セキュリティは、一度学んで終わりではありません。こうして一つずつ、盲点を潰していくことが、結果として最強の防御になるんです。次回は、さらに一歩踏み込んだ「署名の偽造を防ぐEIP-712の仕組み」について深掘りしていきましょう。
それでは、また次回の記事でお会いしましょう!安全なコーディングライフを!
コメント