Ethereum(52) - オーバーフロー⑦(uintの最大値チェック)

今回はuintの最大値に関する動作を確認していきます。

ソースコード

最大値のチェックを行うためのソースコードは下記の通りです。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pragma solidity ^0.4.11;
contract MarketPlaceOverflow {
address public owner;
uint public stockQuantity;

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// 追加在庫数を表示するイベント
event AddStock(uint _addedQuantity);

/// コンストラクタ
function MarketPlaceOverflow() {
owner = msg.sender;
stockQuantity = 0; // 最大値をチェックするため100を0に変更
}

/// 在庫の追加処理
function addStock(uint _addedQuantity) public onlyOwner {
// オーバーフローチェック
require(stockQuantity + _addedQuantity > stockQuantity);

AddStock(_addedQuantity);
stockQuantity += _addedQuantity;
}
}

17行目で初期の在庫数を100から0に変更しています。

これは在庫の追加数としてuintの最大値を設定できるかどうかをテストするためです。

デプロイ

上記ソースのデプロイを行います。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

初期の在庫数(Stock quantity)が0であることが確認できます。

在庫の追加(最大値)

Add Stcok関数を使って、uintの最大値を設定します。

uintの最大値は115792089237316195423570985008687907853269984665640564039457584007913129639935です。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

問題なくuintの最大値が在庫数(Stock quantity)に設定されていることが分かります。

また画像一番下のイベント情報より正しい数値(uintの最大値)が引数として渡されていることも分かります。

在庫の追加(最大値+1)

次に、uintの最大値を超える数値を設定してみます。

[uintの最大値]+1の数値は115792089237316195423570985008687907853269984665640564039457584007913129639936になります。

[在庫数を追加]

[EXECUTE]ボタンを押すと、トランザクションを発行するための画面が表示されます。

ここでパスワードを入力し、[SEND TRANSACTION]ボタンを押したところ次のようなメッセージが表示されました。

[トランザクション実行画面]

どうやら256バイトを超える値は設定できないようです。

uint8の場合は、それを超える数値を設定できたので、データ型の制限ではなくSolidityの引数の制限だと思われます。(ただの推測です)

以上で、オーバーフローに関する動作確認とその対策に関する記事は終わりにします。

Ethereum(51) - オーバーフロー⑥(2回目の改善確認編)

前回修正したスマートコントラクトを使ってオーバーフローが解消されているかどうかを確認します。

デプロイ

デプロイを行います。

デプロイするのは前回改善したMarket Place Overflowです。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

在庫数(Stock quantity)が100であることが確認できます。

在庫の追加

Add Stcok関数を使って、(前回NGとなった)在庫数257の追加を行います。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

前回はオーバーフローが発生したため在庫数が101となってしまいましたが、今回は問題なく357となりました。


念のためイベントをを確認し、正しい引数が渡されているかどうかを確認します。

[イベントの確認]

問題なく257が引数として渡されています。

まとめ

オーバーフローの対策として次の2点を改善しました。

  • 在庫数を追加するときに、追加後の在庫数が増えることを確認。
    (オーバーフローが発生すると追加後の在庫数が減ってしまうため)
  • 追加する在庫数と既存の在庫数のデータ型をuint8からuintに変更。
    (uint8の範囲を超える数値が引数として渡されると、意図しない数値に変換されてしまうため)

2つめの修正ですが、扱える数字が増えただけで根本的な解決にはなっていなような気がします。

指定したデータ型を超える数値を指定したら結局またオーバーフローが発生してしまうと考えられるからです。


次回はuintを超える数値を設定して動作確認を行ってみます。

Ethereum(50) - オーバーフロー⑤(2回目の改善編)

前回は、在庫追加数としてuint8の範囲を超える数値を設定してしまったため、とオーバーフローが発生し257が1に変換されてしまいました。

今回はこの問題を解消していきます。

ソースコード(改善2)

改善策としまして、在庫数を追加する関数(AddStock関数)の引数を、データ型uint8からuintに変更してみます。

uint8の範囲は0~255ですが、uintの範囲は0~2の256乗までとかなりの大きな数字となります。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pragma solidity ^0.4.11;
contract MarketPlaceOverflow {
address public owner;
uint public stockQuantity; // 在庫数 改善2 uint8 ⇒ uint

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// 追加在庫数を表示するイベント
event AddStock(uint _addedQuantity); // 改善2 uint8 ⇒ uint

/// コンストラクタ
function MarketPlaceOverflow() {
owner = msg.sender;
stockQuantity = 100;
}

/// 在庫の追加処理
function addStock(uint _addedQuantity) public onlyOwner { // 改善2 uint8 ⇒ uint
// オーバーフローチェック
require(stockQuantity + _addedQuantity > stockQuantity); // 改善1

AddStock(_addedQuantity);
stockQuantity += _addedQuantity;
}
}

追加在庫数(_addedQuantity)の修正箇所は12行目と21行目で、uint8をuintに変更しています。

また追加在庫数と合わせて、現在の在庫数(stockQuantity)のデータ型もuintに変更しておきます。(4行目)


次回はこの改善でオーバーフローが解消しているかどうかの確認してみます。

Ethereum(49) - オーバーフロー④(改善確認編)

前回修正したスマートコントラクトを使ってオーバーフローが解消されているかどうかを確認します。

デプロイ

デプロイを行います。

デプロイするのは前回改善したMarket Place Overflowです。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

在庫数(Stock quantity)が100であることが確認できます。

在庫の追加①

Add Stcok関数を使って、前回同様に在庫数を156増やしてみます。

確認ダイアログではgas(手数料)に5000000を設定しました。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

前回はオーバーフローが発生したため在庫数が0になってしまいましたが、今回は改善したので在庫の追加が行われず在庫数が100のままになっています。

いちおうオーバーフローが解消しています。


念のためトランザクションも確認します。

[トランザクションの確認]

Gas usedが指定した5000000となっており、最大手数料が消費されているのでトランザクションが失敗したことになります。

在庫の追加②

もう一度在庫の追加を行ってみます。今回は257追加してみます。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

なぜか追加処理が行われ在庫数が101と増えてしまっています。

イベントも確認してみます。

[イベントの確認]

追加の在庫数として257を設定したのですが1となっています。

どうしてでしょうか。

考察

在庫数を追加するAdd Stock関数に257を設定したにも関わらず1という引数が渡されてしまった原因は、引数のデータ型がuint8となっているためです。

データ型のuint8では、0~255までの数値しか表現することができず257が指定されると1に変換されてしまったのです。(桁落ち)

その結果、前回改善した判定が満みたされ在庫追加処理が正常に実行されたということになります。


次回はこの問題を解消するための再度対策を行います。

Ethereum(48) - オーバーフロー③(改善編)

前回オーバーフローが発生したソースを修正します。

ソースコード(改善1)

前回の動作確認では、在庫数(stockQuantity)を追加しているにも関わらず、オーバーフローが発生したため在庫数が減ってしまっていました。

そこで在庫追加後の数が元の在庫数を超えることを確認して、オーバーフローを防いでみます。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pragma solidity ^0.4.11;
contract MarketPlaceOverflow {
address public owner;
uint8 public stockQuantity; // 在庫数

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// 追加在庫数を表示するイベント
event AddStock(uint8 _addedQuantity);

/// コンストラクタ
function MarketPlaceOverflow() {
owner = msg.sender;
stockQuantity = 100;
}

/// 在庫の追加処理
function addStock(uint8 _addedQuantity) public onlyOwner {
// オーバーフローチェック
require(stockQuantity + _addedQuantity > stockQuantity); // 改善1

AddStock(_addedQuantity);
stockQuantity += _addedQuantity;
}
}

23行目で追加後の在庫数が現在の在庫数より大きいことをチェックしています。

次回はこの改善でオーバーフローが解消しているかどうかの確認してみます。

Ethereum(47) - オーバーフロー②(実行編)

前回作成したスマートコントラクトを使ってオーバーフローの動作確認を行います。

アカウンとの役割

今回の動作確認では、アカウントの役割は重要ではないのですが、一応次のようにしておきます。

  • MAIN ACCOUNT (eth.accounts[0])
    コントラクト生成者。マイナー

デプロイ

まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。

デプロイするのはMarket Place Overflowです。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

在庫数(Stock quantity)が100であることが確認できます。

在庫の追加

Add Stcok関数を使って、在庫数を156増やしてみます。

[在庫数を追加]

追加後の在庫数を確認します。

[在庫数を確認]

100 + 156 = 256 になるはずですが、0になってしまいました。

下記のイベント確認すると、在庫追加の引数としては問題なく156が渡されたことが分かります。

[イベントの確認]

ではなぜ在庫数が256にならずに0になってしまったのでしょうか。

計算が合わない理由

原因は在庫数(stockQuntity)がunit8で宣言されているためです。

uint8では8ビットの符号なし整数を表します。範囲としては0~255となります。

256になるとオーバーフローが発生し、0となってしまったのです。


次回はこの問題を解消するための対策を行います。

Ethereum(46) - オーバーフロー①(実装編)

オーバーフローは、スマートコントラクトならではの問題ではありませんが意外と見落としがちです。

オーバーフローが発生すると、データの不整合が発生するので注意が必要です。

ソースコード

オーバーフローを確認するためのサンプルソースは以下の通りです。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pragma solidity ^0.4.11;
contract MarketPlaceOverflow {
address public owner;
uint8 public stockQuantity; // 在庫数

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

/// 追加在庫数を表示するイベント
event AddStock(uint8 _addedQuantity);

/// コンストラクタ
function MarketPlaceOverflow() {
owner = msg.sender;
stockQuantity = 100;
}

/// 在庫の追加処理
function addStock(uint8 _addedQuantity) public onlyOwner {
AddStock(_addedQuantity);
stockQuantity += _addedQuantity;
}
}

マーケットプレイスを想定したコントラクトで、在庫数の追加を行う関数のみを実装しています。

次回は、このスマートコントラクトを使ってオーバーフローになる場合の動作を確認します。

Ethereum(45) - 重要な情報の取り扱い②(実行編)

前回作成した重要な情報を扱うスマートコントラクトの動作確認を行います。

アカウントごとの役割

今回の動作確認では、アカウントの役割は重要ではないのですが、一応次のようにしておきます。

  • ACCOUNT3 (eth.accounts[3])
    コントラクト生成者。
  • ACCOUNT1 (eth.accounts[1])
    マイナー。

デプロイ

まずはデプロイを行います。デプロイアカウントはアカウント3です。

デプロイするのはSecretで、コンストラクタパラメータにはtestを設定します。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

さすがにこの画面からはsecretにどんな文字が設定されているかは確認できません。
(privateで宣言しているため)

トランザクションの確認

コントラクトを生成したときのハッシュを確認します。

[トランザクションの確認]

このトランザクション・ハッシュを引数にして、トランザクションのinputというフィールドを確認します。

gethコンソールから次のコマンドを実行します。

[トランザクションのinputフィールド確認]

1
2
> eth.getTransaction('0xd34e1aa86318225a66a77399f95a835b250ac75139c1c01eb2a80d3636238939').input
"0x6060604052341561000c57fe5b604051610263380380610263833981016040528051015b805161003690600090602084019061003e565b505b506100de565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061007f57805160ff19168380011785556100ac565b828001600101855582156100ac579182015b828111156100ac578251825591602001919060010190610091565b5b506100b99291506100bd565b5090565b6100db91905b808211156100b957600081556001016100c3565b5090565b90565b610176806100ed6000396000f300606060405263ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416637ed6c926811461003a575bfe5b341561004257fe5b610090600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965061009295505050505050565b005b80516100a59060009060208401906100aa565b505b50565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100eb57805160ff1916838001178555610118565b82800160010185558215610118579182015b828111156101185782518255916020019190600101906100fd565b5b50610125929150610129565b5090565b61014791905b80821115610125576000815560010161012f565b5090565b905600a165627a7a72305820548d2d70ba56a4626512317eed38e749eae8a73a9d5322c580ebab786f1c12d90029000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000"

前半部分はコンパイルされたコントラクトのコードですが、後半部分のゼロに囲まれた474657374に注目してみます。

実は、この部分の最初の4はバイト数を表し、その直後の74657374(4バイト)はコントラクトで設定された文字列を表しています。

試しにこのデータをアスキーに変換してみます。

[アスキー変換]

1
2
> web3.toAscii("0x74657374")
"test"

なんとprivateで設定した文字列”test”が簡単に見れてしまいました。

文字列を更新

setSecret関数をコールして、文字列をkoushinに変更します。

[文字列の確認]

再度、トランザクションのハッシュから、inputフィールドを確認します。

[トランザクションのinputフィールド確認]

1
2
> eth.getTransaction('0x1bf2c055abc6e5d0f861c64c52c7a84419c4b80a41edbf954bce7c78b4147392').input
"0x7ed6c926000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000076b6f757368696e00000000000000000000000000000000000000000000000000"

また後半部分のゼロに囲まれた76b6f757368696eに注目してみます。

最初の7はバイト数を表し、6b6f757368696e(7バイト)は更新した文字列を表しています。

このデータをアスキーに変換してみます。

[アスキー変換]

1
2
> web3.toAscii("0x6b6f757368696e")
"koushin"

更新した文字列を確認することができました。

まとめ

privateな変数を宣言しても、トランザクションの中身を確認すれば簡単にそのデータを確認できるということが分かりました。

Ethereumではトランザクションが暗号化されないため、中身を参照すればどんなデータか確認することができるのです。

個人情報のような重要な情報をステートに設定する場合は、暗号化するなどの処理を行うようにしましょう。

Ethereum(44) - 重要な情報の取り扱い①(実装編)

スマートコントラクトは、ステートに情報を保持し他の誰かと情報を連携することができます。

ただこのステートに保持する情報の扱いには気を付ける必要があります。

ソースコード

privateなステート(secret)に文字を格納するだけのサンプルソースを準備します。

[ソースコード]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity ^0.4.11;
contract Secret {
string private secret; // 秘密の文字列

/// コンストラクタ
function Secret(string _secret) {
secret = _secret;
}

/// 秘密の文字列を設定
function setSecret(string _secret) public {
secret = _secret;
}
}

このコントラクトを生成する場合に、秘密の文字列を設定します。(6-8行目)

またこの秘密の文字列はsetSecret関数(11-13行目)を使って更新することはできますが、その文字列を参照する関数はなく、privateで宣言している(3行目)ためその内容を確認することはできない・・・はずです。

本当にprivateで宣言していれば、その情報を参照することはできないのでしょうか。

次回は、このスマートコントラクトの動作確認を行います。

Ethereum(43) - Timestamp Dependence②(実行編)

前回作成した抽選を行うスマートコントラクトの動作確認を行います。

アカウントごとの役割

アカウントの役割は次の通りです。

  • MAIN ACCOUNT (eth.accounts[0])
    コントラクト生成者。抽選会の主催者。
  • ACCOUNT1 (eth.accounts[1])
    抽選申込者1。
  • ACCOUNT2 (eth.accounts[2])
    抽選申込者2。
  • ACCOUNT3 (eth.accounts[3])
    抽選申込者3。
  • ACCOUNT4 (eth.accounts[4])
    マイナー。

デプロイ

まずはデプロイを行います。デプロイアカウントはMAIN ACCOUNTです。

デプロイするのはLotteryです。

[デプロイ]


デプロイ後のコントラクトの状態を確認します。

[コントラクト状態]

主催者(Owner)がMain accountで、応募者の数(Num applicants)が0であることが確認できます。

抽選会に応募

アカウント1,アカウント2、アカウント3から抽選会に応募します。

各アカウントからEnter関数を指定しトランザクションを発行します。

[アカウント1から抽選会に応募]

[アカウント2から抽選会に応募]

[アカウント3から抽選会に応募]


応募したあとのコントラクトの状態を確認します。

応募者(Applicants)は入力欄の右端にある小さい三角ボタンをクリックすると、インデックスを変えて応募者を確認することができます。

[応募1の確認]

[応募2の確認]

[応募3の確認]

応募者の人数(Num applicants)が3で、各応募者(Applicants)がアカウント1,アカウント2、アカウント3であることが確認できます。

抽選(当選者を決める)

抽選の開催者(Main Account)から、Hold関数をコールして当選者を決定します。

[開催者が当選者を決める]


コントラクトの状態を参照し、当選者を確認します。

[当選者の確認]

当選者(Winner address)が、アカウント2であることが確認できます。

また、当選者を決めるパラメータとなったTimestampは1627990495であることが分かります。

抽選時のトランザクション確認

抽選を行った時のトランザクションを確認します。

[当選者を決めた際のトランザクション]

このトランザクションは15909番目のブロックに取り込まれています。

このブロックのタイムスタンプをgethコンソールから確認します。

1
2
> eth.getBlock(15909).timestamp
1627990495

15909番目のブロックのタイムスタンプは1627990495となっています。

この値は、コントラクトで抽選を行った時のタイムスタンプと同じです。

つまり、スマートコントラクトで参照しているblock.timestampはトランザクションが発行されたときのタイムスタンプではなく、ブロックに取り込まれた時のタイムスタンプだということになります。

ブロックのタイムスタンプ

ブロックのタイムスタンプはマイナーがブロックを生成する際に設定するもので、どんな値が設定されるのかはマイナー次第となります。

抽選会にマイナーが応募していたとすると、マイナーは抽選時のトランザクションを検知し自分自身が当選者となるようなタイムスタンプを設定し、ブロックを作成するということが可能です。

今回のサンプルソースは、抽選結果がタイムスタンプにより決定されるといった仕様のため、タイムスタンプを操作され当選者を自由に決められてしまうという脆弱性があったといことになります。

タイムスタンプを操作されると困るような場合は、タイムスタンプだけに依存した仕様にしないように気を付けましょう。


Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×