banner
leaf

leaf

It is better to manage the army than to manage the people. And the enemy.
follow
substack
tg_channel

ブロックチェーン開発実践

区块チェーン技術は主に以下のいくつかの部分から成り立っています。

1. 暗号ハッシュ関数

私たちは皆、関数が 1 つまたは複数の入力値を受け取り、関数の計算を経て 1 つまたは複数の出力値を生成することを知っています。ハッシュ関数は以下のすべての条件を満たします:

・任意の長さの文字列を入力として受け取る。

・固定長の出力値を生成する。

・計算時間が合理的な範囲内である。

上記の条件を満たす限り、関数はハッシュ関数と呼ばれます。簡単な例を挙げると、剰余演算では、任意の数字を 10 で割った結果は 0~9 の間の数字になりますので、剰余演算はハッシュ関数と見なすことができます。

現在、ビットコインなどのデジタル通貨に使用されているハッシュ関数は暗号ハッシュ関数です。暗号ハッシュ関数は、上記のハッシュ関数の 3 つの特徴に加えて、さらに独特な特性を持っています:衝突耐性、隠蔽性、結果のランダム性について、以下でそれぞれ説明します。

(1)衝突耐性

衝突耐性は強衝突耐性と弱衝突耐性に分かれます。強衝突耐性とは、ハッシュ関数 H に対して、異なる x と y の 2 つの値を見つけることができないことを意味します。弱衝突耐性は、ハッシュ関数 H と入力 x に対して、別の y の値を見つけることができないことを意味します。

衝突耐性は本当の「無」衝突ではなく、衝突は確実に存在しますが、ここで強調されているのは衝突を見つける難しさです。ビットコインで使用されているハッシュ関数 SHA256 を例に挙げると、その出力値は 256 ビットであり、結果は 2256 通りの可能性しかありませんが、入力値は無限の可能性を持つことができます。現在、衝突を見つけるための必然的な方法があります:まず 2256+1 個の異なる値を見つけ、それらのハッシュ値を計算すると、結果の集合には必ず重複した値が存在し、これにより衝突が発見されます。しかし、この方法の実現可能性はどうでしょうか?仮に全世界のすべての計算機器を集め、宇宙の誕生から現在まで常に計算を続けた場合、衝突を見つける確率は、次の瞬間に地球が隕石に衝突して破壊される確率と同じです。ここまで読んでいるということは、地球の破壊が起こっていない、つまり衝突が発生していないことを示しています。

現在、数学的に厳密な衝突耐性が証明されている暗号ハッシュ関数は存在せず、市場で言及されている衝突耐性は、一般的に暴力的な解読を除いて、他の方法よりも早く衝突を見つけることができないと考えられています。以前には、衝突耐性があると考えられていたハッシュ関数が後に解読された事例もあります。例えば、MD5 ハッシュアルゴリズムです。ビットコインで使用されている SHA256 ハッシュアルゴリズムも現在は衝突耐性があると考えられていますが、将来的に解読される可能性を排除することはできません。

衝突耐性にはどのような応用がありますか?一般的な例の 1 つはメッセージダイジェスト(Message Digest)です。メッセージダイジェストは、任意の長さの入力に対して、暗号ハッシュ関数を用いて得られるハッシュ値を指します。現在一般的に使用されているハッシュアルゴリズム MD5 を例に挙げると、その計算例は以下の通りです:

image

任意の長さの文字列を入力すると、得られる結果は固定長のランダムな文字列です。衝突耐性が存在するため、この文字列は入力値を一意に表すことができると考えられます。

私たちがインターネット上でソフトウェアをダウンロードする際、どのようにしてダウンロードしたソフトウェアがウェブサイト上のソフトウェアと同じものであることを確認するのでしょうか?この時、メッセージダイジェストが役立ちます。例えば、あるウェブサイトがソフトウェアをダウンロードする際にそのソフトウェアの md5sum 値を提供している場合、私たちはそのソフトウェアをダウンロードした後に手動でそのソフトウェアの md5sum 値を計算し、ウェブサイトの値と比較します。2 つの数値が一致すれば、ダウンロードしたソフトウェアが完全で正確であることを示すことができます。

(2)隠蔽性

H(x)が与えられた場合、x の値を推測することができません。x の値を推測できないだけでなく、x に関する任意の特性(偶奇性など)を推測することもできません。

(3)結果のランダム性

x の値が近いかどうかにかかわらず、ハッシュ計算を経て得られる H(x)は完全にランダムです。

この特性は、たとえ入力値 x の長さが非常に長く、別の入力値 x' が x の値と 1 桁だけ異なる場合でも、ハッシュ関数 H の計算を経て得られる結果には何の関連性もなく、まるで全く異なる x の値を入力したかのようになります。

引き続き md5sum 値の例を挙げます:

liangpeili@LiangXiaoxin:~$ echo 'aschplatform' | md5sum
150fa3630db1d8f576d1266176f6e0f7  -
liangpeili@LiangXiaoxin:~$ echo 'aschplatform1' | md5sum
e915a617b2301631ec14d1ca2c093c63  -
liangpeili@LiangXiaoxin:~$ echo 'aschplatform2' | md5sum
bbb9d830f4a5d47051f9fd19cb0fc75e  -

上記のプログラムからわかるように、たとえ 1 つの非常に小さな値を変更したとしても、ハッシュ計算後の結果は大きく異なります。

この特性にはどのような効果がありますか?特定の結果値 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("最大nonce %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
# 新しいブロックの有効なnonceを見つける
(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(リベスト-シャミール-アドレマン方式)、DSS(デジタル署名標準)などがあります。ビットコインはECDSA(楕円曲線デジタル署名アルゴリズム)を使用してアカウントの公開鍵と秘密鍵を生成し、取引とブロックの検証を行います。デジタル署名の作業原理は以下の通りです:

1)アリスは1対の鍵を生成します。1つはsk(署名鍵)で、公開されていません。もう1つはvk(検証鍵)で、公開されています。この1対の鍵は同時に生成され、数学的に相互に関連しています。また、vkからはskに関する情報を推測することはできません。

2)デジタル署名アルゴリズムは2つの入力を受け取ります:情報Mとskで、デジタル署名Smを生成します。

3)検証関数は情報M、Sm、およびvkを入力として受け取り、返される結果はyesまたはnoです。このステップの目的は、あなたが見た情報Mに対するデジタル署名が確かにアリスのskによって発行されたものであることを検証し、情報と署名が一致するかどうかを確認することです。

手書きの署名とは異なり、手書きの署名は基本的に似ていますが、デジタル署名は入力に大きく影響されます。入力のわずかな変更でも、全く異なるデジタル署名が生成されます。一般的には、情報に対して直接デジタル署名を行うのではなく、情報のハッシュ値に対して署名を行います。暗号ハッシュ関数の衝突耐性から、これにより元の情報に対する署名と同じくらい安全であると考えられます。

3.コンセンサスメカニズム

ブロックチェーンは、すべての取引を記録する分散型の公開台帳と見なすことができ、ブロックチェーン内の各ノードは対等です。これにより、1つの問題が生じます:誰がこの台帳にデータを入力する権限を持っているのか?複数のノードが同時にブロックチェーンにデータを書き込む場合、最終的にどのノードのデータが正しいのか?これは分散型ネットワーク内でデータの一貫性を保つ方法の問題です。コンセンサスメカニズムは、分散型ネットワーク内で、ネットワークに参加する各ノードがデータの一貫性を達成することを指します。ブロックチェーンにおけるコンセンサスメカニズムの役割には、ブロックの生成、ブロックの検証、システムの経済的インセンティブなどの機能が含まれます。

異なるコンセンサスメカニズムは異なるアプリケーションシナリオに適しており、以下は一般的なコンセンサスメカニズムとその適用シナリオの紹介です:

プルーフ・オブ・ワーク(Proof of Work、POW)——ビットコインが使用しているのはプルーフ・オブ・ワークのコンセンサスメカニズムです。このメカニズムでは、計算能力を持つデバイスは誰でもブロックの生成に競争に参加できます。システムは、現在の全ネットワークの計算能力に基づいて難易度を動的に調整し、平均して10分ごとにネットワークが後続のブロックの態度に基づいてどのブロックを承認するかを決定します。一般的に、1回の取引が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.ブロックチェーンの作成

ブロックチェーンは、各要素がブロックであるリンクリストです。ブロックチェーンには、初期化のために創世ブロック(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
Block {
  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
Block {
  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";

image

Solidity データ型#

Solidity は静的型付け言語であり、この章では Solidity のデータ型について詳しく説明します。
主な内容は以下の通りです:
・型の概要と分類
・ブール型
・整数型
・固定小数点型
固定長バイト配列
・有理数と整数定数
・文字列定数
十六進数定数
列挙型

関数型

アドレス型
・アドレス定数
データ位置
配列
構造体
マッピング
・型変換

型推論 演算子 型の概要と分類 Solidity は静的型付け言語であり、一般的な静的型付け言語には C、C++、Java などがあります。静的型付けとは、コンパイル時に各変数(ローカルまたは状態変数)の型を指定する必要があることを意味します(または少なくとも型を推論できる必要があります)。 Solidity のデータ型は見た目は非常にシンプルですが、最も脆弱な場所(オーバーフローなどの問題が発生する可能性がある)でもあります。

注意すべき点は、Solidity の型は占有する空間のサイズに非常に敏感であることです。また、Solidity のいくつかの基本型は複雑な型を組み合わせることができます。 Solidity の型は値型(Value Type)と参照型(Reference Type)の 2 つに分類されます。

さらに、異なる型は異なる演算子と組み合わせることができ、式の演算をサポートし、式の実行順序(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 / 2 ** y は等しいです。左シフトと乗算も等価であり、x <<y と x * 2 ** y は等しいです。シフト演算の結果の正負は演算子の左側の数によって決まります。負の数を右シフトすると、切り捨て時に 0 になります。・負のシフトはできません。つまり、演算子の右側の数は負数にすることはできず、そうでないと実行時例外が発生します。例えば、3>> -1 は無効です。私たちはさらに別の例を見てみましょう。

image

演算子の省略形

C、C++、Java に似て、いくつかの演算子の演算代入は以下の省略形をサポートしています。a += e は a = a + e と等価であり、類似の演算子には:-=、*=、/=、%=、&=、|=、^= があります。

a++ と ++a はそれぞれ a += 1 と a = 1 に等価ですが、式は依然として a の値です。-a と ++a は変更後の値を返します。整数型のオーバーフロー問題

整数型を使用する際は、整数型のサイズと最大値および最小値に特に注意する必要があります。多くのコントラクトはオーバーフロー問題によって脆弱性を引き起こします。例えば、MeiChain の脆弱性については、ブログ記事(https:/learnblockchain.cn/2018/04/25bec-overflow/)を参考にしてください。

以下にオーバーフロー問題に関するいくつかの例を示します。

image

オーバーフローを回避する 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 を返します)。

  • ・ビット演算子:&、|、^(ビット排他的論理和)、~(ビット反転)、<<(左シフト)、>>(右シフト)。

  • インデックス(下付き)アクセス:もし x が bytes1 である場合

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。