
リスクから保護へ:TONスマートコントラクトのセキュリティ上の懸念点と最適化提案
TechFlow厳選深潮セレクト

リスクから保護へ:TONスマートコントラクトのセキュリティ上の懸念点と最適化提案
本稿では、TONブロックチェーン上におけるスマートコントラクトに関するいくつかの特徴と、TON上のスマートコントラクトで見落とされがちな脆弱性について詳細に分析する。
執筆:Beosin
ブロックチェーン技術が急速に進化する今日、TON(The Open Network)は効率的で柔軟性の高いブロックチェーンプラットフォームとして、ますます多くの開発者の注目を集めています。TONの独自なアーキテクチャと特性は、分散型アプリケーション(DApps)の開発に対して強力なツールと豊かな可能性を提供しています。
しかし、機能や複雑さが増すにつれて、スマートコントラクトのセキュリティもますます重要になっています。TON上で使用されるスマートコントラクト言語「FunC」はその柔軟性と高効率性で知られていますが、同時に多くの潜在的なリスクと課題も伴います。安全で信頼できるスマートコントラクトを開発するには、開発者がFunC言語の特性と潜在的なリスクを深く理解する必要があります。
本稿では、TONブロックチェーン上におけるスマートコントラクト関連の特徴、およびTON上で見落とされがちな脆弱性ポイントについて詳細に分析します。

TONの非同期性とアカウントメカニズムの解説
スマートコントラクトの非同期呼び出し
ネットワークシャーディングと非同期通信
TONブロックチェーンは設計上、マスターチェーン(Masterchain)、ワーキングチェーン(Workingchains)、シャードチェーン(Shardchains)の3種類のチェーンに分かれています。
マスターチェーンはネットワーク全体の中核であり、全ネットワークのメタデータとコンセンサスメカニズムを管理します。すべてのワーキングチェーンとシャードチェーンの状態を記録し、ネットワーク全体の一貫性と安全性を確保します。ワーキングチェーンは独立したブロックチェーンで、最大2^32本存在でき、特定のタイプのトランザクションやスマートコントラクトを処理します。各ワーキングチェーンは独自のルールや特性を持ち、異なるアプリケーション要件に対応できます。シャードチェーンはワーキングチェーンのサブチェーンであり、負荷をさらに分割して処理能力と拡張性を向上させます。各ワーキングチェーンは最大2^60個のシャードチェーンに分割可能で、それぞれが部分的なトランザクションを独立して処理することで、効率的な並列処理を実現します。
理論的には、各アカウントが単一のシャードチェーンを専有でき、各アカウントが自身のCOIN/TOKEN残高を独立して管理でき、アカウント間のトランザクションは完全に並列に行えます。アカウント間は非同期メッセージによって情報をやり取りし、シャードチェーン間でのメッセージ転送経路はlog_16(N) - 1となります。ここでNはシャードチェーンの数です。

出典:https://frontierlabzh.medium.com/ton-web3 世界的な weixin-e1d3ae3b3574
TONでは、スマートコントラクトはメッセージの送受信を通じて相互作用します。これらのメッセージは、内部メッセージ(通常はスマートコントラクト同士のやり取り)または外部メッセージ(外部ソースから送信されたもの)のいずれかです。メッセージの伝達プロセスは、対象コントラクトからの即時応答を待つ必要がなく、送信側は他のロジックコードを継続して実行できます。この非同期メッセージングメカニズムは、イーサリアムの同期呼び出しと比較して、より高い柔軟性と拡張性を提供し、応答待ちによるパフォーマンスボトルネックを減らしますが、同時に並行処理や競合条件の問題への対処も求められます。
メッセージのフォーマットと構造
TONにおいて、メッセージは一般的に送信者、受信者、金額、メッセージ本体などの情報を含みます。メッセージ本体には関数呼び出し、データ転送、あるいはカスタムコンテンツが含まれます。TONが使用するメッセージフォーマットは柔軟に定義・拡張可能であり、異なるコントラクト間での多様な情報の効率的な伝達を可能にします。
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(addr)
.store_coins(amount)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_slice(message_body)
.end_cell();
メッセージキューとステート処理
各コントラクトは未処理のメッセージを保存するメッセージキューを維持しています。コントラクトの実行中、キュー内のメッセージを順次処理します。メッセージ処理が非同期であるため、メッセージ受信前にコントラクトのステートは直ちに更新されません。
非同期メッセージングの利点
-
効率的なシャーディングメカニズム:TONの非同期メカニズムはそのシャーディング設計と非常に相性が良く、各シャードがコントラクトのメッセージとステート変更を独立して処理することで、クロスシャードの同期通信に起因する遅延を回避します。この設計により、ネットワーク全体のスループットと拡張性が向上します。
-
リソース消費の低減:非同期メッセージは即時応答を要求しないため、TONのコントラクト実行は複数のブロックにわたって分散して完了できます。これにより、単一ブロック内でのリソース過剰消費を防ぎ、より複雑かつリソースを多く使うスマートコントラクトをサポート可能です。
-
耐障害性と信頼性:非同期メッセージングはシステムの耐障害性を高めます。たとえば、あるコントラクトがリソース制限などによりメッセージに即座に応答できない場合でも、送信側は他のロジックを引き続き処理でき、システム全体が単一コントラクトの遅延で停止することはありません。
非同期コントラクト設計の課題
-
ステート一貫性の問題:メッセージの伝達が非同期であるため、コントラクトのステートは異なるタイミングで異なるメッセージを受け取る可能性があり、開発者は特にステートの一貫性に注意を払う必要があります。コントラクト設計時には、異なるメッセージ順序が引き起こす可能性のあるステート変化を考慮し、あらゆる状況下でシステムが一貫性を保てるようにする必要があります。
-
競合条件と防御策:非同期メッセージ処理には潜在的な競合条件が生じやすく、複数のメッセージが同時にコントラクトステートを変更しようとする可能性があります。開発者は適切なロック機構やトランザクション操作を導入して、ステートの衝突を防ぐ必要があります。
-
セキュリティ上の考慮:非同期コントラクトはクロスコントラクト通信を行う際に、中間者攻撃やリプレイ攻撃を受けやすくなります。そのため、非同期コントラクト設計時にはこうした潜在的なセキュリティリスクを考慮し、タイムスタンプ、乱数、マルチシグなどの手段を用いてそれらを防止する必要があります。
レジャーモデル
Ton(The Open Network)は、ブロックチェーンインフラの設計において、独自のアカウント抽象化とレジャーモデルを採用しています。このモデルの柔軟性は、アカウントのステート、メッセージのやり取り、コントラクトの実行方法に反映されています。
アカウント抽象化
TONのアカウントモデルは、コントラクトに基づく抽象化を採用しており、各アカウントは一つのコントラクトと見なすことができます。これはイーサリアムのアカウント抽象化モデルと似ていますが、さらに柔軟で汎用的です。TONでは、アカウントは資産を保持する容器以上の存在であり、コントラクトコードとステートデータを含んでいます。各アカウントは、コード(Code)、データ(Data)、メッセージ処理(Message Handling)のロジックから構成されます。
アカウント構造:各TONアカウントは一意のアドレスを持っており、このアドレスはアカウントコードのハッシュ値、デプロイ時の初期データ、およびその他のパラメータから生成されます。つまり、同じコードと初期データでも、異なる環境(例:異なるブロックチェーンやシャード)にデプロイすると異なるアドレスになる可能性があります。
柔軟性:各アカウントが独自のコントラクトコードを実行できるため、TONのアカウントは非常に複雑なロジックを実現できます。アカウントは単なる残高保持者ではなく、複雑なステート遷移、アカウント間のメッセージ通信、特定の条件に基づく自動操作などを処理できます。これにより、TONのアカウントモデルは従来のブロックチェーンよりも拡張性と柔軟性に優れています。
レジャー構造
TONのレジャー構造は、大規模な同時トランザクションを効率的に処理するために設計されており、非同期メッセージングとマルチシャード操作をサポートしています。各アカウントのステートはMerkle木構造で保存されており、これによりTONのレジャーは効率的なステート検証が可能です。
ステートストレージ
アカウントのステート情報は永続ストレージに保存され、Merkle木で構成されており、ステートの完全性と安全性を保証します。この設計は、特にクロスシャードトランザクションのシナリオにおいて、効率的なステート照会と検証を可能にします。
アカウントまたはスマートコントラクトのステートには通常以下の内容が含まれます:
-
基本通貨の残高
-
その他通貨の残高
-
スマートコントラクトコード(またはそのハッシュ)
-
スマートコントラクトの永続データ(またはそのMerkleハッシュ)
-
永続ストレージのセル数と使用バイト数に関する統計情報
-
スマートコントラクトの永続ストレージ支払いの最終時刻(実際にはマスターチェーンのブロック番号)
-
通貨の送金とこのアカウントからのメッセージ送信に必要な公開鍵(オプション。デフォルトではaccount_id自体と等しい)。特定のケースでは、ビットコインのトランザクション出力のように、ここに複雑な署名検証コードを配置でき、その場合account_idはこのコードのハッシュ値になります。
これらの情報すべてが各アカウントに必須というわけではありません。例えば、スマートコントラクトコードはスマートコントラクトにのみ必要であり、「シンプル」なアカウントには不要です。また、任意のアカウントは主要通貨(例:基本ワーキングチェーンのマスターチェーンおよびシャードチェーンのGram)の非ゼロ残高を持つ必要がありますが、他の通貨の残高はゼロでも構いません。使用されていないデータを保持しないよう、ワーキングチェーン作成時にsum-product型が定義され、異なるマーカーバイトを使用して異なる「コンストラクタ」を区別します。最終的に、アカウントステート自体はTVMの永続ストレージのセル集合として保存されます。
メッセージの伝達と処理
TONのレジャー構造は、非同期メッセージングを内蔵サポートしており、各アカウントは受信したメッセージを独立して処理し、ステートを更新できます。この非同期メッセージメカニズムにより、ある操作の遅延が他のアカウントの正常動作に影響を与えることなく、アカウント間で複雑なやり取りが可能になります。
Gasモデル
Ton(The Open Network)ブロックチェーンは、独自のGas料金モデルにより、スマートコントラクトの実行効率を大幅に最適化しています。Gasモデルは、スマートコントラクトの実行中に消費されるリソースを測定・制限するために使用されます。イーサリアムなどの従来のブロックチェーンと比べ、TONのモデルはより複雑かつ効率的であり、コントラクト実行中のリソース消費をより正確に管理できます。
細分化されたGas消費測定
TONのGasモデルは、スマートコントラクトが実行中に消費する計算リソース、ストレージ操作、メッセージ伝達コストを精密に測定できます。計算、ストレージ、メッセージ伝達などのリソースを細分化して測定することで、過度に複雑な操作が過剰なリソースを占有するのを防ぎます。Gas消費を制限することで、TONはネットワークのすべてのノードが公平に計算リソースを分配でき、単一コントラクトや操作によるネットワークリソースの過剰消費を回避します。
並列処理とGas最適化
TONはスマートコントラクトの並列処理をサポートしており、複数のコントラクトを異なるシャード上で同時に実行でき、相互にブロッキングしません。この設計により、Gasモデルは並列実行とシャーディングメカニズムと緊密に連携し、複数のシャード上でコントラクトを並列処理することで、Gasの計算と支払いを異なるノードやチェーンに分散でき、ネットワークの混雑を回避しつつ、リソース利用率を最大化します。
動的Gas調整メカニズム
TONのGasモデルには動的調整メカニズムが組み込まれており、ネットワークのリアルタイム負荷に応じてGas料金を調整できます。つまり、ネットワーク負荷が低いときは、ユーザーは低いGas料金でコントラクトを実行でき、低負荷時間帯での操作を促進し、ネットワークリソースの使用を均等化できます。このメカニズムはユーザーエクスペリエンスを向上させるだけでなく、市場原理を通じてリソース使用のピークを制御します。
TONスマートコントラクトで見落とされがちな脆弱性
前回のTONセキュリティ分析記事では、TONエコシステムにおける一般的なセキュリティ脆弱性について詳しく紹介しました。以下表も参照してください:


本稿では、私たちのチームがまとめたTONコントラクトで見落とされがちな脆弱性ポイントに焦点を当てます:
(1) コード可読性の最適化
TONのスマートコントラクトでは、メッセージ送信に関連するデータを数字で保存することがよくあります。以下のコードでは、識別子やデータ格納長を示すために何度も数字が使われており、コードの可読性と保守性が大きく低下します。他の開発者がこのようなコードを読む際、これらの数字の意味や用途を理解するのは困難です。コードの可読性と保守性を高めるため、0x18といった重要な数値をNON_BOUNCEABLEのような名前付き定数として定義することを推奨します。
check_std_addr(address);var msg = begin_cell() store_uint(0x18, 6) ;; nobounce store_slice(address) store_coins(amount) store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) end_cell();send_raw_message(msg, 1);
また、コントラクトの条件判定におけるエラー通知メッセージについても、エラーコードを対応する変数で置き換えることを推奨します。
throw_unless(705, equal_slices(owner_address, sender_address));
(2) end_parse() を使ってデータの完全性を保証
TONコントラクトでは、データ解析は固定順序に従い、元データから順次指定された型のデータを読み込みます。この解析方式により、データの一貫性と正確性が保証されます。以下に例を示します:
() load_data() impure {
slice ds = get_data().begin_parse();
storage::owner = ds~load_msg_addr();
storage::amount = ds~load_uint(256);
storage::data = ds~load_ref();
storage::api_data = ds~load_ref();
ds.end_parse();
}
ここで end_parse() はデータスライス(slice)が空かどうかをチェックし、空でない場合は例外をスローします。これにより、データの形式と内容が予想通りであることが保証されます。end_parse() 関数がデータスライスにまだ残りデータがあると判断した場合、データの解析が予定通り完了していないか、データ形式に問題がある可能性があります。したがって、end_parse() の呼び出しにより、解析中にデータの漏れや異常がないか確認できます。
(3) データ記録とストレージ型の不一致による例外
特に注意すべきは int と uint の読み書き型の一致です。以下のコードでは、store_int() を使って-42というint型の値を格納していますが、それを load_uint() で読み込もうとしています。これにより例外が発生する可能性があります。
() Test_Fuction() {
var cell = begin_cell();
cell = cell.store_int(-42, 32);
var my_cell = cell.end_cell();
slice s = my_cell.begin_parse();
var result = s.load_uint(32);
}
(4) inline_ref と inline 修飾子の適切な使用
まず、inline_ref と inline 修飾子の違いを説明します:
inline:inline 修飾子を使用した関数は、呼び出されるたびにそのコードが呼び出し位置に直接挿入されます。つまり、通常の関数のようにジャンプして実行するのではなく、関数の実際のコードが呼び出し位置にコピーされます。
inline_ref:inline_ref 修飾子を使用した関数は、そのコードが独立したセルに格納されます。関数が呼び出されるたびに、TVMはCALLREF命令を使ってセルに格納されたコードを実行し、呼び出し位置にコードを挿入しません。
したがって、inline 修飾子はシンプルな関数に適しており、関数呼び出しのオーバーヘッドを減らしますが、コントラクトコードの重複を引き起こす可能性があります。一方、inline_ref 修飾子は、比較的複雑または頻繁に呼び出される関数に適しており、関数コードを独立したセルに格納することで効率を高め、コードの重複を回避します。つまり、関数が大きいか複数箇所で呼び出される場合は inline_ref を使用することを推奨します。逆の場合は inline を使用します。
(5) 正しいワーキングチェーンの指定
TONでは最大2^32本のワーキングチェーンを作成でき、各ワーキングチェーンは最大2^60個のシャードに分割可能です。現在は2本のワーキングチェーンしか存在しません:マスターチェーン(-1)と基本チェーン(0)。コントラクト内で宛先アドレスを計算する際には、必ずそのアドレスが属するチェーンIDを明確に指定し、ウォレットアドレスが正しいワーキングチェーン上に生成されるようにする必要があります。誤ったアドレスを生成しないよう、force_chain() を使ってチェーンIDを強制的に指定することを推奨します。
(6) エラーコードの衝突を避ける
コントラクト設計において、規範性を保ち混乱を防ぐため、エラーコードの管理は極めて重要です。TONスマートコントラクトでは、まず各エラーコードがコントラクト内で一意であることを保証し、同一コントラクト内で重複するエラーコードを定義しないことで、エラーコードの混乱や情報の不明瞭さを防ぐ必要があります。また、TONプラットフォームや基盤システムが既に定義している標準エラーコードとの衝突も避けなければなりません。例えば、エラーコード333はチェーンID不一致を意味します。したがって、コントラクトのエラーコードは400~1000の範囲に設定することを推奨します。
(7) 操作完了後にデータを保存し return() を呼び出す
TONスマートコントラクトでは、メッセージ処理はop-codeに応じて異なるロジックを選択します。対応する業務ロジックを完了した後、次の2つの操作が必要です。第一に、データ変更がある場合は、save_data() を呼び出してデータが保存されることを保証しなければならず、これを怠ると変更は無効になります。第二に、return() を呼び出して操作の完了を示さなければならず、これを忘れると throw(0xffff) 例外が発生します。
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
int flags = cs~load_uint(4);
if (flags & 1) {
;; すべてのバウンスメッセージを無視
return ();
}
slice sender_address = cs~load_msg_addr();
load_data();
int op = in_msg_body~load_op();
if ((op == op::op_1())) {
handle_op1();
save_data();
return ();
}
if ((op == op::op_2())) {
handle_op2();
save_data();
return ();
}
if ((op == op::op_3())) {
handle_op3();
save_data();
return ();
}
throw(0xffff);
}
以上のように、TONブロックチェーンは革新的なアーキテクチャと柔軟な開発環境により、分散型アプリケーション開発者の理想のプラットフォームになりつつあります。しかし、スマートコントラクトがTONエコシステムでますます重要な役割を果たすにつれ、コントラクトのセキュリティ問題は決して無視できません。開発者はTONエコシステムの特性を深く理解し、ベストプラクティスを厳守し、セキュリティ監査プロセスを強化して、コントラクトの堅牢性と安全性を確保しなければなりません。これにより初めて、TONプラットフォームの利点を十分に活かし、より安全で信頼性の高い分散型アプリケーションを構築し、エコシステム全体の健全な発展を守ることができるのです。
現在、TONエコシステムは急速に発展しており、大量の資金と活発なユーザーを引き付けています。しかし、それに伴うセキュリティ問題も無視できません。
TechFlow公式コミュニティへようこそ
Telegram購読グループ:https://t.me/TechFlowDaily
Twitter公式アカウント:https://x.com/TechFlowPost
Twitter英語アカウント:https://x.com/BlockFlow_News














