こんにちは!スマートコントラクトの開発の世界へようこそ。
ブロックチェーンやWeb3の技術は本当にワクワクしますよね。「自分の手で世界を変えるようなアプリを作ってみたい!」と、熱い思いを抱いている新人開発者の方も多いはずです。
でも、新しい技術の学習には、ちょっぴり怖いセキュリティの壁が立ちはだかります。「スマートコントラクトのお金が盗まれた」なんてニュースを耳にして、不安になっていませんか?
大丈夫です。一歩ずつ、身近な例えから紐解いていけば、決して恐ろしいものではありません。今回は、スマートコントラクトにおける「整数オーバーフローとアンダーフロー」という、歴史的にも有名な脆弱性と、その現代的な対策について、分かりやすくお話ししていきますね。
—
1. 家の鍵で例える「整数オーバーフロー」の正体
まずは、難しそうな名前の「オーバーフロー」を、私たちの日常生活に置き換えて考えてみましょう。
想像してみてください。あなたの家に、古い「ダイヤル式の自転車の鍵」や「昔ながらの走行距離メーター」があったとします。数字が 0 から 9 までクルクル回る、あのアレです。
もし、メーターが最高値の 9 を指している状態で、さらに「1つ進める(+1する)」とどうなるでしょうか?
そう、メーターは繰り上がって 10 にはならず、なぜかクルッと回って 0 に戻ってしまいますよね。
これが、プログラミングの世界でいう「整数オーバーフロー(Overflow)」です。
デジタル世界の「限界」
コンピュータの世界では、数字を格納する「箱(変数)」の大きさが決まっています。たとえば、最も小さな箱の一つである uint8(符号なし8ビット整数)という型は、0 から 255 までしか数字を入れることができません。
もし、この箱いっぱいの 255 が入っている状態のときに、誤って +1 を足してしまうと……?
コンピュータの計算結果は 256 にならず、なんとクルッと一周して 0 に戻ってしまうのです。
逆に、0 の状態から 1 を引いて(-1して)しまうと、今度は 255 に跳ね上がってしまいます。これを「アンダーフロー(Underflow)」と呼びます。
—
2. なぜこれがスマートコントラクトで大問題になるのか?
「数字がグルグル回るだけなら、大したことないのでは?」と思いましたよね。
しかし、これがブロックチェーン上の「お金(暗号資産やトークン)」を管理するスマートコントラクトで起きたらどうなるでしょうか?
攻撃者は、この仕組みの隙を突いて、次のような悪事を働きます。
1. 残高の無限増殖: 自分の口座の残高が 0 の状態で、わざと引き出し(マイナスの計算)を行い、アンダーフローを起こして残高を 255(莫大な金額)に書き換える。
2. 不正な送金: 実際のトークン以上の金額を誰かに送りつけ、システムをバグらせる。
現実の金庫のダイヤルが壊れて、勝手に札束が増えちゃったようなものです。これでは大変ですよね。過去には、この脆弱性を突かれて数百万ドル規模のハッキング被害が発生したこともあります。
—
3. 昔の泥臭い対策:「SafeMath」という名のパッチ
Solidity(イーサリアムなどで使われるスマートコントラクトの言語)の古いバージョン(0.8.x未満)では、このオーバーフローが自動的にチェックされませんでした。
そのため、昔の開発者たちは SafeMath という専用の「特製ガードマン」ライブラリをわざわざインポートして、すべての足し算や引き算を次のように包み込んでチェックしていました。
// 【昔の書き方のイメージ(Solidity 0.7.x以前)】
import "@openzeppelin/contracts/math/SafeMath.sol";
contract OldBank {
using SafeMath for uint256;
mapping(address => uint256) public balances;
function deposit() public payable {
// SafeMathの .add() を使って安全に足し算をする
balances[msg.sender] = balances[msg.sender].add(msg.value);
}
}
「もし足し算をして数字がおかしくなったら(オーバーフローしたら)、即座に処理をストップ(リバート)させる!」というガードマンを、コードのあちこちに配置していたのです。当時は、これが安全のための必須の作法でした。
—
4. 現代のスマートコントラクト:Solidity 0.8.x 以降の安心設計
「じゃあ、今でもそんな面倒な書き方をしなきゃいけないの?」
安心してください!現代の開発環境では、その心配はぐっと減りました。
私たちが使っている現在の標準的な Solidity(バージョン 0.8.0 以降)では、なんと言語の標準機能として、すべての数学的計算でオーバーフロー・アンダーフローが自動チェックされるようになりました。
特別なガードマン(SafeMath)を呼ぶ必要すらありません。
現代の安全なコード例
それでは、実際のコードを見てみましょう。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20; // 現代の安全なバージョンを指定
contract ModernSafeBank {
// ユーザーの残高を管理するマッピング
mapping(address => uint256) public balances;
// 預金機能
function deposit() public payable {
// Solidity 0.8.x以降では、ここで万が一オーバーフローが起きそうになると
// 自動的にエラーを検知してトランザクション全体が安全にキャンセル(リバート)されます!
balances[msg.sender] += msg.value;
}
// 引き出し機能
function withdraw(uint256 amount) public {
// 残高不足のチェック(もし残高より多く引き出そうとすると、アンダーフローを検知して自動ストップ)
require(balances[msg.sender] >= amount, "残高が足りません!");
// 引き出し処理
balances[msg.sender] -= amount;
// 実際にETHをユーザーに送金する処理(省略)
}
}
このように、pragma solidity ^0.8.20; と宣言するだけで、コンパイラ(翻訳ソフト)が自動的に安全装置を組み込んでくれます。一歩ずつ対策を学んでいけば、現代の開発はとても心強い味方に守られていることが分かりますよね。
—
5. それでも油断してはいけない「現代の盲点」
「じゃあ、もう何も気にしなくていいんだね!」……と言いたいところですが、そこはセキュリティの世界。私たちリサーチャーが現場で直面する「新たな盲点」も少しだけお話ししておきます。
unchecked ブロックの罠
Solidity 0.8.x から、「あえてオーバーフローのチェックを外して、ガス代(手数料)を節約したい!」というガチ勢向けの機能として、unchecked という構文が導入されました。
// ガす代節約のために、オーバーフローチェックをあえて無効化するブロック
uint256 public counter;
function increment() public {
unchecked {
// ここの中の計算は自動チェックがオフになります
counter += 1;
}
}
ループ処理のカウンター(例えば for 文の i++ など、絶対にオーバーフローしないことが確実な変数)でガス代を削るために使われますが、これを誤った場所(ユーザーからの入力値の計算など)で使ってしまうと、一発で昔のような脆弱性が復活してしまいます。
実務で unchecked を見かけたら、「本当にここは安全か?」と疑う、プロの眼差しを持つようにしましょう。
—
まとめ
今回は、スマートコントラクトにおける整数オーバーフロー・アンダーフローの仕組みと、その現代的な対策について解説しました。
- 昔:
SafeMathというライブラリを使って手動で守っていた。 - 現代(0.8.x以降): 言語自体が標準でチェックしてくれるので、基本は安心!
- 注意点:
uncheckedなどの例外的な書き方をする場所では、細心の注意が必要。
セキュリティの世界は奥が深いですが、基礎の仕組みを一つずつ理解していけば、必ず強固で安全なアプリケーションを作れるようになります。
焦らず、一歩ずつ、一緒に楽しく学んでいきましょう!
コメント