区块链技術は主に以下のいくつかの部分から成り立っています。
1. 暗号ハッシュ関数
私たちは皆、関数が 1 つまたは複数の入力値を受け取り、その関数の計算によって 1 つまたは複数の出力値を生成することを知っています。ハッシュ関数は以下のすべての条件を満たします:
・任意の長さの文字列を入力として受け取ることができる。
・固定長の出力値を生成する。
・計算時間が合理的な範囲内である。
上記の条件を満たす限り、関数はハッシュ関数と呼ばれます。簡単な例を挙げると、剰余演算では、任意の数字を 10 で割った結果は 0 から 9 の間の数字になりますので、剰余演算はハッシュ関数と見なすことができます。
現在、ビットコインなどのデジタル通貨に使用されているハッシュ関数は暗号ハッシュ関数です。暗号ハッシュ関数は、上記のハッシュ関数の 3 つの特徴に加えて、より独特な特性を持っています:衝突耐性、隠蔽性、結果のランダム性について以下で説明します。
(1)衝突耐性
衝突耐性は強衝突耐性と弱衝突耐性に分けられます。強衝突耐性とは、ハッシュ関数 H に対して、異なる x と y の 2 つの値を見つけることができないことを意味します。弱衝突耐性は、ハッシュ関数 H と入力 x に対して、別の y の値を見つけることができないことを意味します。
衝突耐性は本当の「無」衝突ではなく、衝突は確実に存在します。ここで強調されているのは、衝突を見つける難しさです。ビットコインで使用されているハッシュ関数 SHA256 を例にとると、その出力値は 256 ビットであり、結果は 2256 通りの可能性しかありませんが、入力値は無限の可能性を持つことができます。現在、必ず衝突を見つける方法があります:まず 2256+1 個の異なる値を見つけ、それらのハッシュ値を計算すれば、結果の集合には必ず重複する値が存在し、これにより 1 回の衝突が発見されます。しかし、この方法の実現可能性はどうでしょうか?仮に世界中のすべての計算機器を集め、宇宙の誕生から現在までずっと計算を続けた場合、1 回の衝突を見つける確率は、次の瞬間に地球が隕石に衝突して破壊される確率と同じです。ここまで読んでいるということは、地球の破壊が起こっていない、つまり衝突が発生していないことを示しています。
現在、数学的に厳密な衝突耐性が証明されている暗号ハッシュ関数は存在しません。現在市場で言及されている衝突耐性は、一般的に暴力的な解読を除いて、他の方法でより早く衝突を見つけることができないと考えられています。以前は衝突耐性があると考えられていたハッシュ関数が後に解読された事例もあります。例えば、MD5 ハッシュアルゴリズムです。ビットコインが使用している SHA256 ハッシュアルゴリズムも現在は衝突耐性があると考えられていますが、将来的に解読される可能性は否定できません。
衝突耐性にはどのような応用がありますか?一般的な例としては、メッセージダイジェスト(Message Digest)があります。メッセージダイジェストとは、任意の長さの入力に対して、暗号ハッシュ関数を用いて得られるハッシュ値を指します。現在一般的に使用されているハッシュアルゴリズム MD5 を例にとると、その計算例は以下の通りです:
任意の長さの文字列を入力すると、得られる結果は固定長のランダムな文字列になります。衝突耐性が存在するため、この文字列は入力値を一意に表すことができると考えられます。
私たちがインターネットでソフトウェアをダウンロードする際、どのようにしてダウンロードしたソフトウェアがウェブサイト上のソフトウェアと同じものであるかを確認するのでしょうか?この時、メッセージダイジェストが役立ちます。例えば、あるウェブサイトがソフトウェアをダウンロードする際にそのソフトウェアの md5sum 値を提供している場合、私たちはそのソフトウェアをダウンロードした後に手動でそのソフトウェアの md5sum 値を計算し、ウェブサイト上の値と比較することができます。2 つの数値が一致すれば、ダウンロードしたソフトウェアが完全であることが確認できます。
(2)隠蔽性
H(x)が与えられた場合、x の値を推測することができない。x の値を推測できないだけでなく、x に関する奇偶性などの特徴を推測することもできない。
(3)結果のランダム性
x の値が近いかどうかにかかわらず、ハッシュ計算を経て得られる H(x)は完全にランダムである。
この特性は、入力値 x の長さが非常に長く、別の入力値 x' が x の値と 1 桁だけ異なる場合でも、ハッシュ関数 H の計算を経て得られる結果には何の関連性もなく、まるで全く異なる 2 つの x 値を入力したかのようになります。
md5sum 値の例を続けます:
liangpeili@LiangXiaoxin:~$ echo 'aschplatform' | md5sum
150fa3630db1d8f576d1266176f6e0f7 -
liangpeili@LiangXiaoxin:~$ echo 'aschplatform1' | md5sum
e915a617b2301631ec14d1ca2c093c63 -
liangpeili@LiangXiaoxin:~$ echo 'aschplatform2' | md5sum
bbb9d830f4a5d47051f9fd19cb0fc75e -
上記のプログラムからわかるように、わずかに異なる値を変更しただけでも、ハッシュ計算後の結果には大きな違いが生じます。
この特性にはどのような効果がありますか?特定の結果値 H(x)に対して、条件を満たす入力値 x を見つけようとする場合、暴力的な試行以外に方法はありません。SHA256 を例にとると、その出力結果の長さは 256 ビットであり、SHA256 計算を経て結果の最初の桁が 0 になるような x 値を見つけようとする場合、その期待される試行回数は 2 になります。もし連続して 10 桁が 0 のハッシュ値を得たい場合、期待される計算回数は 210 になります。結果の範囲を調整することで、計算回数(結果の難易度とも考えられる)を調整することができ、これがビットコインの難易度調整の原理でもあります。
Python で実装されたマイニングアルゴリズムは以下の通りです:
#!/usr/bin/env python
#coding:utf-8
proof-of-workアルゴリズムの例
import hashlib
import time
max_nonce = 2 ** 32 # 40億
def proof_of_work(header, difficulty_bits):
難易度ターゲットを計算する
target = 2 ** (256-difficulty_bits)
for nonce in xrange(max_nonce):
hash_result = hashlib.sha256(str(header)+str(nonce)).hexdigest()
# この結果が有効かどうかを確認する、ターゲットより下にあるか
if long(hash_result, 16) < target:
print("Nonce %dで成功" % nonce)
print("ハッシュは%sです" % hash_result)
return (hash_result, nonce)
print("最大ノンスで%d回試行して失敗しました" % nonce)
return nonce
if name == "main":
nonce = 0
hash_result = ''
0から15ビットの難易度
for difficulty_bits in xrange(16):
difficulty = 2 ** difficulty_bits
print("難易度: %ld (%dビット)" %(difficulty, difficulty_bits))
print("検索を開始します...")
# 前のブロックのハッシュを含む新しいブロックを作成
# トランザクションのブロックを偽造します - ただの文字列です
new_block = 'トランザクションを含むテストブロック' + hash_result
# 新しいブロックの有効なノンスを見つける
(hash_result, nonce) = proof_of_work(new_block, difficulty_bits)
# 結果を見つけるのにかかった時間を記録
end_time = time.time()
elapsed_time = end_time - start_time
print("経過時間: %.4f秒" % elapsed_time)
if elapsed_time > 0:
# ハッシュ毎秒の推定
hash_power = float(long(nonce)/elapsed_time)
print("ハッシュパワー: %ldハッシュ/秒" % hash_power)
2.デジタル署名
現実の仕事や生活の中で、私たちは署名の方法で文書の承認を表現します。他の人はあなたの署名を認識でき、あなたの署名を偽造することはできません。デジタル署名は現実の署名の電子的な実装であり、現実の署名の特徴を完全に達成するだけでなく、さらに優れたものにすることができます。一般的なデジタル署名アルゴリズムにはRSA(Rivest-Shamir-Adleman Scheme)、DSS(Digital Signature Standard)などがあります。ビットコインはECDSA(楕円曲線デジタル署名アルゴリズム)を使用してアカウントの公開鍵と秘密鍵を生成し、取引やブロックの検証を行います。デジタル署名の原理は以下の通りです:
1)アリスは1対の鍵を生成します。1つはsk(署名鍵)で、公開されていません。もう1つはvk(検証鍵)で、公開されています。この1対の鍵は同時に生成され、数学的に相互に関連しています。また、vkからskに関する情報を推測することはできません。
2)デジタル署名アルゴリズムは、情報Mとskの2つの入力を受け取り、デジタル署名Smを生成します。
3)検証関数は、情報M、Sm、およびvkを入力として受け取り、結果をyesまたはnoとして返します。このステップの目的は、あなたが見た情報Mに対するデジタル署名が実際にアリスのskによって発行されたものであることを確認し、情報と署名が一致しているかどうかを確認することです。
手書きの署名とは異なり、手書きの署名は基本的に似ていますが、デジタル署名は入力に大きく影響されます。入力のわずかな変更でも、全く異なるデジタル署名が生成されます。一般的には、情報に対して直接デジタル署名を行うのではなく、情報のハッシュ値に対して署名を行います。暗号ハッシュ関数の衝突耐性からわかるように、これにより元の情報に対する署名と同じくらい安全です。
3.コンセンサスメカニズム
ブロックチェーンは、すべての取引を記録する分散型の公開台帳と見なすことができ、ブロックチェーン内の各ノードは対等です。これにより、1つの問題が生じます:誰がこの台帳にデータを入力する権限を持っているのか?複数のノードが同時にブロックチェーンにデータを書き込む場合、最終的にどのノードのデータが正しいのか?これは分散型ネットワーク内でデータの一貫性を維持する方法の問題です。コンセンサスメカニズムは、分散型ネットワーク内で、ネットワークに参加する各ノードがデータの一貫性を達成することを指します。ブロックチェーンにおけるコンセンサスメカニズムの役割には、ブロックの生成、ブロックの検証、システムの経済的インセンティブなどの機能も含まれます。
異なるコンセンサスメカニズムは異なるアプリケーションシナリオに適しています。以下は一般的なコンセンサスメカニズムとその適用シナリオの紹介です:
プルーフ・オブ・ワーク(Proof of Work、POW)——ビットコインが使用しているのはプルーフ・オブ・ワークのコンセンサスメカニズムです。このメカニズムでは、計算能力を持つデバイスは誰でもブロックの生成に競争に参加できます。システムは、現在の全ネットワークの計算力に基づいて難易度を動的に調整し、平均して10分ごとにネットワークが後続のブロックの態度に基づいてどのブロックを承認するかを決定します。一般的に、取引は6回の確認(約1時間)を経た後、安全で不可逆的であると見なされます。中本聡はビットコインを設計する際、プルーフ・オブ・ワークメカニズムの背後にある核心思想は「one cpu one vote」であり、ビットコインを完全に非中央集権的なシステムに設計することを期待していました。誰でもコンピュータなどの端末を使用して参加できるようにするためです。後にマイニングプールの出現により、ビットコインシステムの計算力は比較的集中しましたが、現在もプルーフ・オブ・ワークメカニズムは公的ブロックチェーンに最も適したコンセンサスメカニズムと見なされています。
·プルーフ・オブ・ステーク(Proof of Stake、POS)——プルーフ・オブ・ステークメカニズムは2013年に提案され、最初にPeercoinで使用されました。プルーフ・オブ・ワークメカニズムでは、ブロックを生成する確率は所有する計算力に比例します。それに対して、プルーフ・オブ・ステークメカニズムでは、ブロックを生成する難易度はそのシステム内での所有権に比例します。プルーフ・オブ・ステークメカニズムでは、ブロックの生成プロセスは次のようになります:ノードは保証金(トークン、資産、名声などの価値を持つ物品)を提供して合法的なブロックが新しいブロックになることを賭け、その利益は担保資本の利息と取引手数料になります。提供する保証金が多いほど、記帳権を得る確率が高くなります。新しいブロックが生成されると、ノードは相応の利益を得ることができます。プルーフ・オブ・ステークメカニズムの目的は、プルーフ・オブ・ワークメカニズムで大量のエネルギーが浪費される問題を解決することです。悪意のある参加者は、保証金が没収されるリスクがあります。
·委任プルーフ・オブ・ステーク(Delegated Proof of Stake、DPOS)——プルーフ・オブ・ワークとプルーフ・オブ・ステークメカニズムは、どちらもブロックチェーンデータの一貫性の問題を解決できますが、上記で述べたように、プルーフ・オブ・ワークメカニズムには計算力の集中(マイニングプール)の問題があり、プルーフ・オブ・ステークメカニズムは保証金の量に基づいてブロック生成の難易度を調整する方法が「マタイ効果」を引き起こす可能性があります。つまり、大量のトークンを持つアカウントの権利がますます大きくなり、記帳権を支配する可能性があります。前者2つの問題を解決するために、後にプルーフ・オブ・ステークメカニズムに基づく改良アルゴリズムが提案されました——委任プルーフ・オブ・ステークメカニズム。このコンセンサスメカニズムでは、システム内の各トークン保有者が特定の代表者に投票でき、最終的に得票率が上位101名の代表者がシステムの記帳権を得ることができます。これらの代表者は、定められた時間にブロックを生成し、ブロック生成の利益を得ます。委任プルーフ・オブ・ステークメカニズムは、コンセンサスの効率を向上させることができ(ビットコインが10分ごとに1つのブロックを生成するのに対し、このメカニズムでは10秒以内に1つのブロックを生成できます)、エネルギーの浪費やマタイ効果を回避できるため、多くの新興公的ブロックチェーン(例えばEOS)の選択肢となっています。
4.取引のブロックチェーン
ビットコインネットワークでは、各取引が完了すると、その取引はビットコインのP2Pネットワークにブロードキャストされます。マイナーはこの取引を受け取るだけでなく、同じ時間帯に記録されていない他のすべての取引も受け取ります。マイナーの仕事は、これらすべての取引をパッケージ化して1つの取引ブロックにすることです。具体的なプロセスは次の通りです:
1)マイナーはこれらの取引記録をペアにし、マークルツリーを通じてルートノードの値を計算します。
2)ルートノードと前のブロックのハッシュ値を組み合わせて、マイナーがプルーフ・オブ・ワークの入力値として使用するChallenge Stringを生成します。
3)マイナーはプルーフ・オブ・ワークを完了し、その証明を他のノードが検証できるように公開します。同時に、最初の記録(この記録はコインベーストランザクションとも呼ばれます)で自分にマイニング報酬を割り当てます。
4)他のノードが検証を通過すると、そのブロックは新しいブロックとしてブロックチェーンに追加されます。
5)マイナーは他の取引記録の取引手数料を集めて自分に配分することもできます。
ビットコインの誕生とブロックチェーン技術の進展は、私たちに大きな想像力を与えました。現在、インターネットは情報の伝達を完了しましたが、ブロックチェーン技術はインターネットに価値の伝達をもたらすかもしれません。ブロックチェーン技術のインフラストラクチャやアプリケーションシナリオは、現在のインターネット技術のレベルに発展するまでには一定の時間が必要かもしれませんが、ブロックチェーン技術の潜在能力は侮れません。300行のコードでブロックチェーンシステムを開発する
このセクションでは、Node.jsを使用してシンプルなブロックチェーンシステムを実装します。わずか300行のコードで実現します。
ブロックとブロックチェーンの作成
ブロックチェーンは、ブロックをハッシュポインタで接続した鎖であり、ブロックはその基本単位です。ここでは、ブロックのデータ構造を設計することから始めます。
1.ブロックの作成
ブロックはブロックチェーンを構築する基本単位であり、ブロックには少なくとも以下の情報が含まれている必要があります。
·index:ブロックがブロックチェーン内での位置。
·timestamp:ブロックが生成された時間。
·transactions:ブロックに含まれる取引。
·previousHash:前のブロックのハッシュ値。
·hash:現在のブロックのハッシュ値。
最後の2つの属性previousHashとhashはブロックチェーンの本質であり、ブロックチェーンの改ざん防止特性はこの2つの属性によって保証されます。
上記の情報に基づいて、Blockクラスを作成します。
const SHA256 = require('crypto-js/sha256');
class Block {
// コンストラクタ
constructor(index, timestamp) {
this.index = index;
this.timestamp = timestamp;
this.transactions = [];
this.previousHash = '';
this.hash = this.calculateHash();
}
// ブロックのハッシュ値を計算する
calculateHash() {
return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.transactions) + this.nonce).toString();
}
// 現在のブロックに新しい取引を追加する
addNewTransaction(sender, recipient, amount) {
this.transactions.push({
sender,
recipient,
amount
})
}
// 現在のブロック内の取引情報を確認する
getTransactions() {
return this.transactions;
}
}
上記のBlockクラスの実装では、crypto-jsのSHA256をブロックのハッシュアルゴリズムとして使用しています。これはビットコインで使用されているハッシュアルゴリズムでもあります。transactionsは一連の取引オブジェクトのリストであり、各取引の形式は次のようになります:
{
sender: sender,
recipient: recipient,
amount: amount
}
また、Blockクラスには3つのメソッドcalculateHash、addNewTransaction、getTransactionsを追加しました。それぞれ、現在のブロックのハッシュを計算する、新しい取引を現在のブロックに追加する、現在のブロックのすべての取引を取得するために使用されます。
ブロックの構築が完了したら、次のステップはブロックを組み立ててブロックチェーンにすることを考えることです。
2.ブロックチェーンの作成
ブロックチェーンはリンクリストであり、リンクリストの各要素は1つのブロックです。ブロックチェーンには創世ブロック(Genesis Block)が必要であり、これはブロックチェーンの最初のブロックであり、手動で生成する必要があります。Blockchainクラスを作成する際には、創世ブロックの生成を考慮する必要があります。以下はコードの例です:
class Blockchain {
constructor() {
this.chain = [this.createGenesisBlock()];
}
// 創世ブロックを作成する
createGenesisBlock() {
const genesisBlock = new Block(0, "01/10/2017");
genesisBlock.previousHash = '0';
genesisBlock.addNewTransaction('Leo', 'Janice', 520);
return genesisBlock;
}
// 最新のブロックを取得する
getLatestBlock() {
return this.chain[this.chain.length - 1];
}
// ブロックをブロックチェーンに追加する
addBlock(newBlock) {
newBlock.previousHash = this.getLatestBlock().hash;
newBlock.hash = newBlock.calculateHash();
this.chain.push(newBlock);
}
// 現在のブロックチェーンが有効かどうかを検証する
isChainValid() {
for (let i = 1; i < this.chain.length; i++){
const currentBlock = this.chain[i];
const previousBlock = this.chain[i - 1];
// 現在のブロックのハッシュが正しいかどうかを検証する
if(currentBlock.hash !== currentBlock.calculateHash()){
return false;
}
// 現在のブロックのpreviousHashが前のブロックのハッシュと等しいかどうかを検証する
if(currentBlock.previousHash !== previousBlock.hash){
return false;
}
}
return true;
}
}
Blockchainクラスでは、創世ブロックを作成するメソッドを実装しました。創世ブロックには前のブロックが存在しないため、previousHashは0に設定されています。また、この日はLeoとJaniceの結婚記念日であり、LeoがJaniceに520トークンを送金したことから、取引が創世ブロックに記録されました。最後に、この創世ブロックをコンストラクタに追加することで、ブロックチェーンには1つの創世ブロックが含まれることになります。getLatestBlockメソッドとaddBlockメソッドの意味は明らかであり、それぞれ最新のブロックを取得することと、新しいブロックをブロックチェーンに追加することを意味します。最後のisChainValidメソッドは、ブロックのハッシュ値を検証することによって、ブロックチェーン全体が有効であるかどうかを検証します。すでにブロックチェーンに追加されたブロックデータが改ざんされた場合、このメソッドはfalseを返します。このシナリオについては次の部分で検証します。
3.ブロックチェーンのテスト
ここまでで、最もシンプルなブロックチェーンを実装しました。この部分では、作成したブロックチェーンをテストします。方法は、ブロックチェーンに2つの完全なブロックを追加し、ブロックの内容を変更してブロックチェーンの改ざん防止特性を示すことです。
まず、testCoinという名前のブロックチェーンを作成します。Blockchainクラスを使用して新しいオブジェクトを作成すると、現在は創世ブロックのみを含むはずです:
const testCoin = new Blockchain();
console.log(JSON.stringify(testCoin.chain, undefined, 2));
このプログラムを実行すると、結果は次のようになります:
[
{
"index": 0,
"timestamp": "01/10/2017",
"transactions": [
{
"sender": "Leo",
"recipient": "Janice",
"amount": 520
}
],
"previousHash": "0",
"hash": "23975e8996cd37311c7fd0907f9b2511c3bf23cf9c9147cca329dec76d7b544e"
}
]
次に、2つのブロックを新たに作成し、それぞれに1つの取引を含めます。そして、これら2つのブロックを順にtestCoinブロックチェーンに追加します:
block1 = new Block('1', '02/10/2017');
block1.addNewTransaction('Alice', 'Bob', 500);
testCoin.addBlock(block1);
block2 = new Block('2', '03/10/2017');
block2.addNewTransaction('Jack', 'David', 1000);
testCoin.addBlock(block2);
console.log(JSON.stringify(testCoin.chain, undefined, 2));
次の結果が得られます:
{
"index": 0,
"timestamp": "01/10/2017",
"transactions": [
{
"sender": "Leo",
"recipient": "Janice",
"amount": 520
}
],
"previousHash": "0",
"hash": "23975e8996cd37311c7fd0907f9b2511c3bf23cf9c9147cca329dec76d7b544e"
},
{
"index": "1",
"timestamp": "02/10/2017",
"transactions": [
{
"sender": "Alice",
"recipient": "Bob",
"amount": 500
}
],
"previousHash": "23975e8996cd37311c7fd0907f9b2511c3bf23cf9c9147cca329dec76d7b544e",
"hash": "32b96fa0bba9a7353e67498d822fb0c1f89c307098295c288459cb44dbc5d0f1"
},
{
"index": "2",
"timestamp": "03/10/2017",
"transactions": [
{
"sender": "Jack",
"recipient": "David",
"amount": 1000
}
],
"previousHash": "32b96fa0bba9a7353e67498d822fb0c1f89c307098295c288459cb44dbc5d0f1",
"hash": "3a0b9a0471bb474f7560968f2f05ff93306cfc26be7f854a36dc4fea92018db2"
}
]
testCoinは現在3つのブロックを含んでいます。創世ブロックの他に、残りの2つのブロックは私たちが追加したものです。各ブロックのpreviousHash属性が前のブロックのハッシュ値を正しく指しているかどうかに注意してください。
この時点でisChainValidメソッドを使用して、このブロックチェーンの有効性を検証できます。console.log(testCoin.isChainValid())の返り値はtrueです。
ブロックチェーンの改ざん防止特性はどこにありますか?まず、最初のブロックの取引を変更してみましょう。最初のブロックで、アリスがボブに500を送金したと仮定します。アリスは後悔し、ボブに100を支払いたいだけだと考え、取引情報を次のように変更します:
block1.transactions[0].amount = 100;
console.log(block1.getTransactions())
アリスはブロックチェーンの取引情報を確認し、すでに100に変更されていることを見て安心して去りました。ボブはそれを見て、取引が改ざんされたことに気付きました。そこでボブは証拠を集め始めます。彼はどのようにしてblock1の取引が人為的に改ざんされたものであることを証明できるのでしょうか?ボブはisChainValidメソッドを呼び出して、現在のtestCoinが無効であることを証明できます。なぜなら、testCoin.isChainValid()の返り値はfalseになるからです。しかし、なぜtestCoin.isChainValid()がfalseを返すのでしょうか?その背後にある論理を見てみましょう:まずアリスが取引の内容を変更した場合、この時点でblock1のハッシュ値は以前の取引から計算されたハッシュ値とは異なることが確実です。この2つの値の違いはisChainValidがfalseを返すトリガーになります。つまり、以下のコードの実装機能です:
if(currentBlock.hash !== currentBlock.calculateHash())
{
return false;
}
そうすると、アリスは取引内容を変更する際にblock1のハッシュも変更すればよいのではないでしょうか?アリスは他のブロックの内容も改ざんすることができます:
block1.transactions[0].amount = 100;
block1.hash = block1.calculateHash();
console.log(testCoin.isChainValid())
この場合、最終的な結果も依然としてfalseになります。なぜでしょうか?それは以下のコードのためです:
if(currentBlock.previousHash !== previousBlock.hash){
return false;
}
各ブロックは前のブロックのハッシュ値を保存しており、1つのブロックを変更するだけでは不十分であり、次のブロックが保存しているpreviousHashも変更する必要があります。もし私たちがblock2のハッシュ値を安全に保存しているなら、アリスは既存のデータを発見されずに改ざんすることは不可能です。実際のブロックチェーンプロジェクトでは、1つのブロックを変更するには、そのブロック以降のすべてのブロックを変更する必要がありますが、これは実現不可能なことです。このブロックチェーンの「ハッシュポインタ」の特性は、ブロックチェーンデータの改ざん防止性を保証します。
プルーフ・オブ・ワーク#
実装されたブロックチェーンシステムはまだ比較的シンプルであり、電子通貨システムで解決すべき「二重支払い」問題を解決していません。システム全体が健康に運営されるためには、システム内に一定の経済的インセンティブメカニズムを設計する必要があります。ビットコインシステムでは、中本聡が「プルーフ・オブ・ワーク」メカニズムを設計し、システム内の経済的インセンティブ問題と二重支払い問題を解決しました。以下ではプルーフ・オブ・ワークアルゴリズムの原理と実装について説明します。
1. プルーフ・オブ・ワークアルゴリズム
健康に運営されるブロックチェーンシステムは常に取引を生成し、サーバーが以下の作業を行う必要があります:定期的に一定の時間(ビットコインは 10 分、Asch は 10 秒)の取引を 1 つのブロックにパッケージ化し、既存のブロックチェーンに追加します。しかし、ブロックチェーンシステムには多くのサーバーが存在する可能性があり、どのサーバーがブロックをパッケージ化したものとして採用されるのでしょうか?この問題を解決するために、ビットコインでは「プルーフ・オブ・ワーク」と呼ばれるアルゴリズムを採用して、どのサーバーがブロックをパッケージ化するかを決定し、相応の報酬を与えます。
プルーフ・オブ・ワークアルゴリズムは次のように簡単に説明できます:ある時間帯に複数のサーバーがその時間帯の取引をパッケージ化し、パッケージ化が完了した後、ブロックヘッダー情報と共に SHA256 アルゴリズムで計算を行います。ブロックヘッダーと報酬取引のコインベースにはそれぞれ nonce という変数があります。計算結果が難易度値(この概念については後で説明します)の要件を満たさない場合、nonce の値を調整して計算を続けます。もしあるサーバーが最初に難易度値を満たすブロックを計算した場合、そのブロックをブロードキャストすることができます。他のサーバーが問題ないことを確認した後、既存のブロックチェーンに追加され、皆が次のブロックの競争を再開します。このプロセスは「マイニング」とも呼ばれます。
プルーフ・オブ・ワークアルゴリズムは SHA256 ハッシュアルゴリズムを採用しており、このアルゴリズムの特徴は特定の結果を得ることが難しいことですが、一度適切な結果が計算されると、その有効性を検証するのは非常に簡単です。ビットコインシステムでは、難易度要件を満たすブロックを見つけるのに約 10 分かかりますが、それが有効であるかどうかを検証するのは瞬時のことです。次のセクションのコード実装でこれを確認します。
簡単な例を挙げると、ある人々がコインを投げるゲームをしていると仮定します。各人は 10 枚のコインを持ち、順番に 10 枚のコインを投げ、最後に 10 枚の表と裏の並びの結果を見ます。結果には順序があるため、常に 210 通りの可能性があります。今、ルールが定められています:1 ラウンドのゲームで、最初に 4 枚のコインがすべて表である結果を出した人が報酬を得ることができます。したがって、皆は 10 枚のコインを投げて結果を集計し始めます。最初の 4 枚がすべて表である可能性は 26 通りであり、したがって 1 人がその結果を得るための期待される試行回数は 24 回です。もし表の枚数が多いほど、各人の試行回数が増えることになります。この枚数が難易度です。ゲームをする人が増えると、結果の最初の 6 枚、最初の 8 枚が表であることを要求することができます。このようにして、各ラウンドの時間はほぼ同じになります。これがビットコインの計算力が大幅に増加しても、平均して 10 分ごとに 1 つのブロックを生成し続ける理由です。
上記でプルーフ・オブ・ワークとは何かを説明しましたが、それを私たちのブロックチェーンアプリケーションにどのように追加するのでしょうか?
任意のデータは SHA256 計算を経て 256 ビットのバイナリ数値を得ることができます。最初の部分の連続する 0 の数を「難易度値」として調整することができます。例えば、最後のブロックが SHA256 計算を経て最初の桁が 0 になることを要求する場合、平均して 2 回の計算でそのような結果を得ることができます。しかし、もし連続して 10 桁がすべて 0 であることを要求する場合、平均して 210 回の計算が必要になります。システムは計算結果の連続する 0 の数を調整することで、難易度調整の目標を達成できます。
ブロックのヘッダー情報に nonce という変数を追加します。nonce の値を調整し続けて、全体のブロックのハッシュ値を再計算し、計算結果が難易度要件を満たすまで続けます。
2. プルーフ・オブ・ワークのコード実装
前のセクションの概念に基づいて、現在のブロックチェーンアプリケーションを改造し始めます。まず、Block クラスに nonce 変数を追加します:
class Block {
constructor(index, timestamp) {
this.index = index;
this.timestamp = timestamp;
this.transactions = [];
this.previousHash = '';
this.hash = this.calculateHash();
this.nonce = 0;
}
...
}
次に、Block クラスに mineBlock メソッドを追加します:
mineBlock(difficulty) {
console.log(`ブロック${this.index}をマイニング中`);
while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
this.nonce++;
this.hash = this.calculateHash();
}
console.log("ブロックがマイニングされました: " + this.hash);
}
mineBlock メソッドは、難易度値に基づいて nonce を見つけるためのものです。適切な nonce が見つかるまで、ブロックを提出することはできません。ここでの difficulty は、結果の最初の部分が連続して 0 である桁数を指します。計算されたハッシュ値が要件を満たさない場合、nonce を 1 増やし、再度ブロックのハッシュ値を計算します。
次に、Blockchain クラスに難易度値を定義します:
constructor() {
this.chain = [this.createGenesisBlock()];
this.difficulty = 2;
}
マイニングプロセスをブロックチェーンに新しいブロックを追加するプロセスに適用します:
addBlock(newBlock) {
newBlock.previousHash = this.getLatestBlock().hash;
newBlock.mineBlock(this.difficulty);
this.chain.push(newBlock);
}
これで、アプリケーションの改造が完了しました。次に、この部分を追加した後のコードをテストします。
まず、1 つのブロックだけを追加します。
block1 = new Block('1', '02/10/2017');
block1.addNewTransaction('Alice', 'Bob', 500);
testCoin.addBlock(block1);
console.log(block1)
計算結果は次の通りです:
ブロック1をマイニング中
ブロックがマイニングされました: 005fed00324fcbe1f0ab1703afe94e45a99e197a7df142e669444687f9513e57
ブロック {
index: '1',
timestamp: '02/10/2017',transactions: [ { sender: 'Alice', recipient: 'Bob', amount: 500 } ],
previousHash: '31b15cc32d6772f237dcf298d5b7a2417f298f40ce6d8d5fbe07958141df7a4c',
hash: '005fed00324fcbe1f0ab1703afe94e45a99e197a7df142e669444687f9513e57',
nonce: 419
}
nonce 値と hash 値に注意してください。nonce 値は計算回数を示し、hash 値は最終的な結果です。今回設定した難易度値は 2 であり、期待される計算回数は 28 回です(hash の 1 文字は 4 ビットを表します)。もし難易度値を 3 に変更した場合はどうなるでしょうか?計算結果は次のようになります:
ブロック1をマイニング中
ブロックがマイニングされました: 000b7f17beaf58bc8fea996a9fed11103ed27ad6d63818b87d89a440cd9757b5
ブロック {
index: '1',
timestamp: '02/10/2017',
transactions: [ { sender: 'Alice', recipient: 'Bob', amount: 500 } ],
previousHash: '31b15cc32d6772f237dcf298d5b7a2417f298f40ce6d8d5fbe07958141df7a4c',
hash: '000b7f17beaf58bc8fea996a9fed11103ed27ad6d63818b87d89a440cd9757b5',
nonce: 4848
}
計算回数が増加したことがわかります。難易度値が増加するにつれて、CPU の計算回数も指数関数的に増加し、それに伴い消費される時間も長くなります。
ブロックチェーンとのインタラクションのための API を提供する#
1. マイニング報酬
関連 API を実装する前に、まずマイニング報酬とは何かを見てみましょう。
上記でマイニングの原理を説明し、プルーフ・オブ・ワークアルゴリズムを実装しましたが、サーバーはなぜ自分の CPU リソースを提供してブロックをパッケージ化するのでしょうか?その答えは、マイニング時に報酬メカニズムが存在するからです。マイナーは一定の時間の取引をパッケージ化した後、ブロックの最初の取引の位置に新しい取引を作成します。この取引には送信者がなく、受信者は誰でも設定できます(一般的には自分のアドレスに設定します)。報酬の額はどうなるのでしょうか?現在、ビットコインのマイナーは 1 つのブロックをパッケージ化するごとに 12.5BTC の報酬を得ています。この報酬取引はシステムによって保証されており、他のノードによって検証されることができます。
ここにはいくつかの問題があります。まず、報酬金額の問題です。ビットコインが最初に発行されたとき、各ブロックの報酬は 50BTC でした。その後、4 年ごとに半減し、2018 年 7 月にはすでに 12.5BTC になっています。次に、マイナーは複数の報酬取引を作成したり、報酬金額を増やしたりできるのでしょうか?もちろん、マイナーはそうすることができますが、そうするとブロードキャストされたブロックは他のノードによって検証されません。他のノードはブロックを受け取った後、合法性を検証し、システムのルールに合わない場合はそのブロックを破棄します。そのため、そのブロックは最終的にブロックチェーンに追加されることはありません。
2. コードのリファクタリング
現在のコードを API を通じて外部に提供する形式に改造するためには、以下の処理を行う必要があります:
1)Blockchain クラスに currentTransactions 属性を追加し、最新の取引を収集し、次のブロックにパッケージ化する準備をします:
constructor() {
this.chain = [this.createGenesisBlock()];
this.difficulty = 3;
this.currentTransactions = [];
}
2)Block クラスの addNewTransaction メソッドを Blockchain クラスに移動します。
3)Block クラスと Blockchain クラスを出力(export)し、app.js を blockchain.js に名前変更します。
最終的な blockchain.js の内容は次のようになります:
const SHA256 = require('crypto-js/sha256');
// ブロッククラス
class Block {
constructor(index, timestamp) {
this.index = index;
this.timestamp = timestamp;
this.transactions = [];
this.previousHash = '';
this.hash = this.calculateHash();
this.nonce = 0;
}
calculateHash() {
return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.transactions) + this.nonce).toString();
}
mineBlock(difficulty) {
console.log(`ブロック${this.index}をマイニング中`);
while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
this.nonce++;
this.hash = this.calculateHash();
}
console.log("ブロックがマイニングされました: " + this.hash);
}
getTransactions() {
return this.transactions;
}
}
// ブロックチェーンクラス
class Blockchain {
constructor() {
this.chain = [this.createGenesisBlock()];
this.difficulty = 3;
this.currentTransactions = [];
}
addNewTransaction(sender, recipient, amount) {
this.currentTransactions.push({
sender,
recipient,
amount
});
}
createGenesisBlock() {
const genesisBlock = new Block(0, "01/10/2017");
genesisBlock.previousHash = '0';
genesisBlock.transactions.push({
sender: 'Leo',
recipient: 'Janice',
amount: 520
});
return genesisBlock;
}
getLatestBlock() {
return this.chain[this.chain.length - 1];
}
addBlock(newBlock) {
newBlock.previousHash = this.getLatestBlock().hash;
newBlock.mineBlock(this.difficulty);
this.chain.push(newBlock);
}
isChainValid() {
for (let i = 1; i < this.chain.length; i++){
const currentBlock = this.chain[i];
const previousBlock = this.chain[i - 1];
if(currentBlock.hash !== currentBlock.calculateHash()){
return false;
}
if(currentBlock.previousHash !== previousBlock.hash){
return false;
}
}
return true;
}
}
module.exports = {
Block,
Blockchain
}
注意:上記では、Blockchain 内の createGenesisBlock メソッドのコードも修正しました。
3. Express を使用して API サービスを提供する
API サービスを提供するために、Node.js で最も人気のある Express フレームワークを使用します。ブロックチェーンは以下の 3 つのインターフェースを外部に提供します:
・POST/transactions/new:新しい取引を追加する、形式は JSON です。
・GET/mine:現在の取引を新しいブロックにパッケージ化します。
・GET/chain:現在のブロックチェーンを返します。
基本コードは次の通りです:
const express = require('express');
const uuidv4 = require('uuid/v4');
const Blockchain = require('./blockchain').Blockchain;
const port = process.env.PORT || 3000;
const app = express();
const nodeIdentifier = uuidv4();
const testCoin = new Blockchain();
// インターフェースの実装
app.get('/mine', (req, res) => {
res.send("新しいブロックをマイニングします。");
});
app.post('/transactions/new', (req, res) => {
res.send("新しい取引を追加します。");
});
app.get('/chain', (req, res) => {
const response = {
chain: testCoin.chain,
length: testCoin.chain.length
}
res.send(response);
})
app.listen(port, () => {
console.log(`ポート${port}でサーバーが起動しました`);
});
次に、/mine および /transactions/new ルートを完成させ、いくつかのログ機能を追加します(必須ではありません)。
まず、/transactions/new ルートを見てみましょう。このインターフェースでは、次のような JSON 形式の取引を受け取ります:
{
"sender": "私のアドレス",
"recipient": "他の誰かのアドレス",
"amount": 5
}
次に、この取引を現在のブロックチェーンの currentTransactions に追加します。ここでは body-parser モジュールを使用します。最終的なコードは次のようになります:
const bodyParser = require("body-parser");
const jsonParser = bodyParser.json();
app.post('/transactions/new', jsonParser, (req, res) => {
const newTransaction = req.body;
testCoin.addNewTransaction(newTransaction);
res.send(`取引${JSON.stringify(newTransaction)}がブロックチェーンに正常に追加されました。`);
});
次に /mine ルートです。このインターフェースの機能は、現在未パッケージ化の取引を収集し、それを新しいブロックにパッケージ化します。報酬取引を追加し(ここでは 50 に設定し、受信アドレスは uuid とします)、難易度要件を満たすマイニングを行い、新しいブロック情報を返します。コード実装は次のようになります:
app.get('/mine', (req, res) => {
const latestBlockIndex = testCoin.chain.length;
const newBlock = new Block(latestBlockIndex, new Date().toString());
newBlock.transactions = testCoin.currentTransactions;
// 新しいブロックのマイニング報酬を得る
newBlock.transactions.unshift({
sender: '0',
recipient: nodeIdentifier,
amount: 50
});
testCoin.addBlock(newBlock);
testCoin.currentTransactions = [];
res.send(`新しいブロックをマイニングしました${JSON.stringify(newBlock, undefined, 2)}`);
});
これで、コードはほぼ完成しました。最後に、ログを記録するミドルウェアを追加します:
app.use((req, res, next) => {
var now = new Date().toString();
var log = `${now}: ${req.method} ${req.url}`;
console.log(log);
fs.appendFile('server.log', log + '\n', (err) => {
if (err) console.error(err);
});
next();
})
res.send(`取引
${JSON.stringify(newTransaction)}がブロックチェーンに正常に追加されました。`);
API をテストする
Node Server.js を使用してアプリケーションを起動し、Postman を使用して現在の API をテストします。
アプリケーションを起動すると、現在のブロックチェーンには創世ブロックのみが存在するはずです。/chain を使用して現在のブロックチェーン情報を取得します。
現在のブロックチェーンには 1 つのブロックしかないことがわかります。では、どのようにして新しい取引を追加するのでしょうか?
参考資料:
・プルーフ・オブ・ワークの実装
https://www.savjee.be/2017/09/Implementing-proof-of-work-javascript-blockchain/
・ブロックチェーンを構築することで学ぶ
https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
・Go でのブロックチェーンの構築
https://jeiwan.cc/posts/building-blockchain-in-go-part-2/
・ビットコインホワイトペーパー
https://bitcoin.org/bitcoin.pdf
スマートコントラクト#
前の 2 章で、私たちはスマートコントラクトとイーサリアムの核心概念について初歩的に理解しました。この章からは、Solidity を使用してスマートコントラクトを書く方法を段階的に紹介します。この章では、コントラクトが通常どのような内容を含むかについて説明します。2 つの視点から議論します。1 つは Solidity コントラクトファイル構造の観点から、もう 1 つはコントラクト内容の観点からです。 Solidity ファイル構造 Solidity コントラクトのソースファイルは「sol」という拡張子を使用します。ファイル構造から見ると、コントラクトファイルは通常以下のいくつかの部分を含みます:コントラクトバージョン宣言、他のソースファイルのインポート、コントラクトの定義およびコメントなど。 コントラクトバージョン宣言 Solidity のソースファイルはバージョン宣言を行う必要があり、コンパイラにこのソースファイルがサポートするコンパイラバージョンを通知します。不適合な新しいコンパイラバージョンが出現した場合、古いソースファイルのコンパイルを拒否します。バージョン更新ログを定期的に読むことは良い習慣であり、特に大きなバージョンがリリースされるときは重要です。
バージョン宣言の方法は次の通りです: pragma solidity ^0.4.0: このようなソースファイルは Solidity0.4.0 以前のバージョンおよび Solidity0.5.0 以降のバージョンとは互換性がありません(「」記号はバージョン番号の第 2 部分を制御します)。通常、バージョン番号の第 3 部分のアップグレードは小さな変更に過ぎず(互換性の問題はありません)、したがって特定のバージョンを指定するのではなく、この方法を使用します。これにより、コンパイラにバグが修正される場合、コードを変更する必要がなくなります。 より複雑なバージョン宣言を使用する場合、その宣言式は npm と一致させる必要があります。参考にしてください:https:/docs.npmjs.com/misc/semver. 他のソースファイルのインポート Solidity は import 文をサポートしており、JavaScript(ES6)に似ていますが、Solidity には「デフォルトエクスポート」の概念はありません。 グローバルインポート、インポート形式は次の通りです:
import "filename";
「filename」からすべてのグローバルシンボル(filename が他のファイルからインポートしたものを含む)を現在のグローバルスコープにインポートします。
カスタム名前空間インポート、インポート形式は次の通りです:
import as symbolName from "filename";
グローバルな名前空間 symbolName を作成し、メンバーは filename のグローバルシンボルから来ます。
ES6 互換ではない簡略化された構文があり、次のように等価です:
import {symbol1 as alias,symbol2}from "filename";
Solidity データ型#
Solidity は静的型付け言語であり、この章では Solidity のデータ型について詳しく説明します。
主な内容は以下の通りです:
・型の概要と分類
・ブール型
・整数型
・定長浮動小数点型
定長バイト配列
・有理数と整数リテラル
・文字列リテラル
十六進数リテラル
列挙型
●
関数型
●
アドレス型
・アドレスリテラル
データ位置
配列
構造体
マッピング
・型変換
型推論 演算子 型の概要と分類 Solidity は静的型付け言語であり、一般的な静的型付け言語には C、C++、Java などがあります。静的型付けとは、コンパイル時に各変数(ローカルまたは状態変数)の型を指定する必要があることを意味します(または少なくとも型を推論できること)。 Solidity のデータ型は見た目は非常にシンプルですが、最も脆弱性が発生しやすい場所でもあります(オーバーフローなどの問題が発生することがあります)。
もう 1 つ注意すべき点は、Solidity の型はそのサイズに非常に敏感であることです。また、Solidity のいくつかの基本型は複雑な型を組み合わせることができます。 Solidity の型は 2 つのカテゴリに分けられます:値型(Value Type)と参照型(Reference Type)。
さらに、異なる型は異なる演算子と組み合わせることができ、式の演算をサポートし、式の実行順序(Order of Evaluation of Expression)に従って実行されます。 値型 値型は 32 バイト以内のサイズを占め、値型変数は代入または引数として渡す際に常に値コピーが行われます。
値型には以下が含まれます:・ブール型(Boolean)・整数型(Integer)・定長浮動小数点型(Fixed Point Number) 定長バイト配列(Fixed-size Byte Array) 有理数と整数リテラル(Rational and Integer Literal) 文字列リテラル(String Literal) 十六進数リテラル(Hexadecimal Literal) 列挙型(Enum)・関数型(Function Type) アドレス型(Address) アドレスリテラル(Address Literal) 参照型 参照型には主に以下が含まれます:配列(Array)、構造体(Struct)、およびマッピング(Mapping)。
ブール型(Boolean) ブール型は bool キーワードを使用して宣言され、宣言方法は次の通りです: bool isActive; boo1is0k=false;/ デフォルト値を持つ ブール型の可能な値は定数値 true および false です。
ブール型がサポートする演算子は以下の通りです。・!, 論理否定。・&&, 論理積。・||, 論理和。・=, 等しい。・!=, 等しくない。 注意:演算子「&&」と「||」は短絡演算子であり、例えば fx) || gy) の場合、fx) が真である場合、gy) は実行されません。fx) && gy) の場合、fx) が偽である場合、gy) は実行されません。整数型(Integer) Java などの言語では short、int、long を使用して整数型を表しますが、Solidity の整数型は int に型のビット数を付けた形式で表されます。この方法は Go 言語と一致します。
整数型のキーワードには int8、int16 から int256 までがあり、数字は 8 ビット単位で増加します。
対応する符号なし整数型は uint8 から uint256 までで、uint および int はデフォルトで uint256 および int256 に対応します。 宣言方法は次の通りです: int8 x = -1; int16 y = 2; int32 z 整数型がサポートする演算子は以下の通りです。
・比較演算子:<=、<、=、!=、>=、>(ブール値 true または false を返します)。
ビット演算子:&、|、^(排他的論理和)、~(ビット反転)。
算術演算子:+、-、一元演算子「-」、一元演算子「+」、*、/、%(剰余)、(累乗)、<<(左シフト)、>>(右シフト)。
説明:
・整数除算は常に切り捨てられますが、定数を演算する場合(定数は後で説明します)、切り捨てられません。・整数を 0 で割ると例外が発生します。つまり、x/0 は無効です。・右シフトと除算は等価であり、x>>y と x/2y は等しいです。左シフトと乗算も等価であり、x<<y と x*2y は等しいです。シフト演算の結果の正負は演算子の左側の数によって決まります。負の数を右シフトすると、切り捨てが行われ、0 になります。・負のシフトは行えません。つまり、演算子の右側の数は負数にできず、そうでないと実行時例外が発生します。例えば、3>>-1 は無効です。 もう一つの例を見てみましょう。
演算子の省略
C、C++、Java に似て、いくつかの演算子の演算代入は以下の省略形をサポートします。a += e は a = a + e に等しいです。類似の演算子には:-=、*=、/=、%=、&=、|=、^= があります。
a++ と ++a はそれぞれ a += 1 と a = 1 に等しいですが、式は依然として a の値です。a-- と --a はそれぞれ a -= 1 と a = 1 に等しいですが、式は依然として a の値です。整数型のオーバーフロー問題 整数型を使用する際には、整数のサイズとその最大値および最小値に特に注意する必要があります。多くのコントラクトはオーバーフロー問題によって脆弱性を引き起こします。例えば、MeiChain の脆弱性については、ブログ記事(https:/learnblockchain.cn/2018/04/25bec-overflow/)を参考にしてください。
以下にオーバーフロー問題に関するいくつかの例を示します。
オーバーフローを避ける 1 つの方法は、演算後に結果値をチェックすることです。例えば、上記の k に対してチェックを行う場合、assert (k>= i) を使用します。また、加算、減算、乗算、除算を行う際には、OpenZeppelin の SafeMath ライブラリを使用することをお勧めします。コードの GitHub アドレスは https:/github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol です。
定長浮動小数点型(Fixed Point Number)#
定長浮動小数点型の機能は、他の言語の浮動小数点型 float や double とほぼ同じで、浮動小数点数を表すために使用されます。しかし、定長浮動小数点型には異なる点があります。宣言時に型が占めるサイズと小数点の桁数を指定する必要がありますが、従来の浮動小数点型 float や double は通常値が異なり、占有する空間も異なります。 Solidity はまだ定長浮動小数点型を完全にはサポートしておらず、変数を宣言するためには使用できますが、値を割り当てることはできません。定長浮動小数点型の宣言方法は次の通りです:
ufixed32x1 f;
ufixed32x1 fi=0.1;//UnimplementedFeatureError:まだ実装されていません
fixed/ufixed は符号付きおよび符号なしの固定ビット浮動小数点数を表します。キーワードは ufixedMxN および fixedMxN であり、M はこの型が占めるビット数を示し、8 ビット単位で、8 から 256 ビットまでの範囲で指定できます。N は小数点の桁数を示し、0 から 80 までの範囲で指定できます。 fixed/ufixed はそれぞれ fixed128x18 および ufixed128x18 を表します。
サポートされる演算子は以下の通りです。
・比較演算子:<=、<、=、!=、>=、>(ブール値 true または false を返します)。
算術演算子:+、-、一元演算子「-」、一元演算子「+」、*、/、%(剰余)。 注意:これはほとんどの言語の float や double とは異なり、ここでの M は全体の数が占める固定ビット数を示し、整数部分と小数部分を含みます。したがって、小さなビット数(M が小さい)を使用して浮動小数点数を表す場合、小数部分はほとんど全体の空間を占めることになります。
定長バイト配列(Fixed-size Byte Array)#
定長バイト配列は、固定サイズの配列であり、各要素は 1 バイトです。可変長配列は値型ではないため、別のセクションで紹介します。
定長バイト配列の宣言方法は次の通りです:
byte bte;
bytes1 bt1 = 0x01;
bytes2 bt2 = "ab";
bytes3 bt3 = "abc";
キーワードには bytes1、bytes2、bytes3、…、bytes32 があり、1 ずつ増加します。byte は bytes1 を表します。実際の使用において、定長バイト配列はしばしば文字列の代わりに使用されます(例えば、宣言の b2 および bt3)。 定長バイト配列がサポートする演算子は以下の通りです。
-
・比較演算子:<=、<、=、!=、>=、>(ブール値 true または false を返します)。
-
・ビット演算子:&、|、^(ビット排他的論理和)、~(ビット反転)、<<(左シフト)、>>(右シフト)。
-
インデ