こんにちは!ブロックチェーンの世界へようこそ。
スマートコントラクトの開発に足を踏み入れたばかりだと、耳慣れない用語がたくさん出てきて「難しそうだな…」と不安になってしまいますよね。でも、安心してください。一歩ずつ、身近な例えから紐解いていけば必ず理解できるようになりますよ。
今回は、スマートコントラクトの歴史において何度も大事件を引き起こしてきた「整数オーバーフロー・アンダーフロー」というちょっと怖い名前の脆弱性について、一緒に優しく学んでいきましょう!
—
1. 家の鍵の仕組みで例える「整数オーバーフロー」
まずは、プログラミングの世界で「数値を扱う仕組み」がどうなっているのか、身近なもので例えてみましょう。
皆さんのご自宅にある、古いダイヤル式の自転車の鍵や、数字を回して開ける南京錠を思い浮かべてみてください。あの鍵は、数字が 0 から 9 まで回ると、次はクルッと回ってまた 0 に戻りますよね?
デジタル世界における数値も、実はこれと似ています。
例えば、コンピュータの世界で「0から255までの数字しか入れられない小さな箱(8ビットの変数)」があったとします。この箱に限界である 255 が入っている状態で、さらに 1 を足そうとするとどうなるでしょうか?
ダイヤル式の鍵と同じように、数字が限界を超えてクルッと一周し、なんと突然 0 に戻ってしまうのです。これが「整数オーバーフロー(Overflow)」と呼ばれる現象です。
逆に、箱の中身が 0 の状態で 1 を引こうとすると、今度は逆回転して最大の 255 に跳ね上がってしまいます。こちらは「アンダーフロー(Underflow)」と呼ばれます。
なぜこれがスマートコントラクトで大問題になるの?
ブロックチェーン上で動くスマートコントラクトは、お金(暗号資産やトークン)の残高を管理しています。もし、攻撃者がこの「数値を一周させるバグ」を悪用したらどうなるでしょうか?
例えば、自分の口座にトークンが少ししか入っていない状態で、わざと計算を狂わせて残高を爆発的に増やし(アンダーフローやオーバーフローの悪用)、無限にお金を引き出してしまう……という大泥棒のような手口が可能になってしまうのです。実際に過去には、これが原因で数億円規模のトークンが不正に盗み出される事件が起きています。
—
2. 昔の苦労と、Solidity 0.8以降の「自動セキュリティ装置」
こうした恐ろしい計算ミスを防ぐため、開発者たちはこれまで様々な工夫をしてきました。
昔のやり方:SafeMathライブラリの導入
Solidityのバージョン 0.7 以前では、プログラマー自身が「今から足し算をするけど、オーバーフローしないか事前にチェックしてね!」という専用のガードマン(ライブラリ)を用意する必要がありました。それが SafeMath です。
当時のコードを少しだけ覗いてみましょう。
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
// OpenZeppelinのSafeMathライブラリをインポート(昔の必須テクニック)
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/math/SafeMath.sol";
contract OldStyleBank {
using SafeMath for uint256; // uint256型にSafeMathの機能を適用
mapping(address => uint256) public balances;
function deposit() public payable {
// 昔は危ないので直接「+」を使わず、SafeMathの「add」関数を使っていました
balances[msg.sender] = balances[msg.sender].add(msg.value);
}
}
「あれ、普通の足し算 (+) じゃなくて .add() なんて書かなきゃいけないの面倒だな…」と思いましたよね? その通り、うっかり普通の + や - を使ってしまうと、すぐに見落としによる脆弱性が生まれてしまう、泥臭い開発環境だったのです。
現在のやり方:Solidity 0.8以降のデフォルト防衛
しかし、ご安心ください!私たちがいま普段使いする Solidity 0.8以降 では、言語のコンパイラ(翻訳システム)自体に、このチェック機能が標準装備されるようになりました。
現在の書き方を見てみましょう。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20; // 0.8以降のバージョンを指定
contract ModernBank {
mapping(address => uint256) public balances;
function deposit() public payable {
// 0.8以降は、普通の「+」を使うだけで自動的にオーバーフローをチェックしてくれます!
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public {
// もし残高より多い金額を引き出そうとすると、
// 自動的にエラー(Revert)が発生してトランザクションが安全に中止されます
require(balances[msg.sender] >= amount, "残高が足りません!");
balances[msg.sender] -= amount;
// 実際の送金処理(省略)
}
}
Solidity 0.8以降では、特別なライブラリを使わなくても、普通の四則演算 (+, -, *, /) を行った時点で、もしオーバーフローやアンダーフローが起きそうになると自動的に処理をストップ(リバート)してくれます。まるで、自動でロックがかかる頑丈な金庫が標準装備されたようなものですね!
—
3. 「じゃあ、0.8使えばもう安心だね?」という油断大敵な落とし穴
「おっ、じゃあSolidity 0.8を選んでおけば、セキュリティの勉強はもう終わりでいいね!」と思ったそこのあなた。ちょっと待ってください。セキュリティリサーチャーとして、現場のリアルな注意点を一つだけお伝えしておきます。
実は、特定の条件下では、この自動チェックをわざと「オフ」にしたい場面が存在します。それが unchecked ブロックです。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract LoopExample {
uint256 public counter = 0;
function incrementLoop() public {
// ガスの節約(手数料削減)のために、あえてオーバーフローチェックを外す特殊な書き方
unchecked {
// ループのカウンターなど、絶対にオーバーフローしないことが確実な場合に使われます
counter += 1;
}
}
}
ブロックチェーンの世界では、プログラムの計算量が増えれば増えるほど、手数料(ガス代)が高くなります。そのため、熟練の開発者の中には「ここは絶対にオーバーフローしない数学的証明があるから、ガスの節約のためにチェックを外そう」と unchecked を使う人がいます。
しかし、この unchecked の使い方を少しでも誤ると、一瞬で致命的なセキュリティホールに直結します。 初めてコントラクトを書くうちは、この unchecked はなるべく使わない、あるいは使うとしても熟練者のレビューを必ず受けるようにしてくださいね。
—
4. まとめ:一歩ずつ安全なコードを書くために
今回は、整数オーバーフロー・アンダーフローの仕組みと、Solidityのバージョンの進化について見てきました。今日のポイントをサクッと振り返ってみましょう!
1. オーバーフローとは: 数字の箱の限界を超えると、値がクルッとゼロに戻ってしまう現象。
2. 昔の対策: SafeMath というライブラリを使って、手動で安全性をチェックしていた。
3. 今の対策: Solidity 0.8以降なら、通常の計算でも自動でチェックしてくれるので圧倒的に安全になった。
4. 注意点: ガス代節約のための unchecked の乱用は禁物!
セキュリティの世界は奥が深いですが、基礎の仕組みを一つずつ知っていけば、確実に安全なアプリケーションを作れるようになります。
焦らず、楽しく、一緒に安全なWeb3の未来を作っていきましょう!それではまた次回の記事でお会いしましょう!
コメント