Goのブロックチェーン。 パート2:作業証明

こんにちは、Habr! 記事「 Goでブロックチェーンを構築する。パート2:Proof-of-Work 」の翻訳を紹介します。



内容

  1. Goのブロックチェーン。 パート1:プロトタイプ
  2. Goのブロックチェーン。 パート2:作業証明
  3. Goのブロックチェーン。 パート3:読み取り専用メモリとコマンドラインインターフェイス
  4. Goのブロックチェーン。 パート4:トランザクション、パート1
  5. Goのブロックチェーン。 パート5:アドレス
  6. Goのブロックチェーン。 パート6:トランザクション、パート2
  7. Goのブロックチェーン。 パート7:ネットワーク




エントリー



前回の記事では、ブロックチェーンデータベースの基礎となる非常に単純なデータ構造を構築しました。 また、ブロックをチェーン接続で追加しました。各ブロックは前のブロックに関連付けられています。 残念ながら、ブロックチェーンの実装には重大な欠点が1つあります。ブロックをチェーンに追加するのは単純すぎて安価です。



ビットコインとブロックチェーンの基礎の1つは、新しいブロックの追加は非常に複雑な作業であるべきだということです。 そして今、私たちはこの欠点を修正するつもりです。



作業証明(PoW)



ブロックチェーンの重要な考え方は、新しいブロックを追加するには複雑な作業を行う必要があるということです。 ブロックチェーンの信頼性と全体性を高めるのは、この複雑な作業です。 さらに、この複雑な作業に対して報酬が支払われます(これが、採掘のためにコインを獲得する方法です)。



このメカニズムは実際の生活に似ています。報酬を獲得し、人生を守るために一生懸命働く必要があります。 ブロックチェーンでは、ネットワークの一部の参加者(マイナー)がネットワークの維持に取り組み、ブロックチェーンに新しいブロックを追加し、作業に対する報酬を受け取ります。 彼らの仕事の結果、ブロックは信頼性の高い方法でブロックチェーンに埋め込まれ、ブロックチェーンデータベース全体の安定性が保証されます。 作業を完了した人もその実装を証明する必要があることに注意してください。



この全体が「ハードワークを行い、それを証明します」-メカニズムは、Proof-of-Work(作業の証明)と呼ばれます。 それは大きな計算能力を必要とするため複雑です:高性能のコンピューターでさえすぐに実行することはできません。 さらに、平均して1時間あたり約6ブロックを作成するために、この作業の複雑さは徐々に増加しています。 ビットコインでは、このような作業の目標は、特定の要件を満たすブロックハッシュを見つけることです。 このハッシュは証拠として機能します。 したがって、証拠の検索は実際の作業です。



注意点:作業証明アルゴリズムは次の要件を満たしている必要があります。作業は複雑である必要がありますが、証明は単純でなければなりません。 証拠の検証は通常、外部の誰かに転送されるため、この検証に長時間かかることはありません。



ハッシング



この部分はハッシュについてです。 この概念に精通している人は、この部分をスキップできます。



ハッシュは、一部のデータのハッシュを取得するプロセスです。 ハッシュは、計算対象のデータの一意の表現です。 ハッシュ関数は、任意のサイズのデータ​​に対して特定のサイズのハッシュを受け取る関数です。 ハッシュの主な機能は次のとおりです。



  1. 初期データはハッシュから復元できません。 ハッシュは暗号化ではありません
  2. 特定のデータのハッシュは常に一意で一意です。
  3. データの1バイトを変更すると、まったく異なるハッシュが生成されます






ハッシュ関数は、データの整合性を検証するために広く使用されています。 多くのソフトウェアベンダーは、ソフトウェアでチェックサムを公開しています。 ファイルをダウンロードした後、ハッシュ関数をフィードし、結果のハッシュをソフトウェア開発者が公開したものと比較する必要があります。



ブロックチェーンでは、ブロックの整合性を保証するためにハッシュが使用されます。 ハッシュアルゴリズムの入力には、前のブロックのハッシュが含まれているため、チェーン内のブロックを変更することができません(少なくとも非常に複雑です)。ブロック自体のハッシュと、それに続くすべてのブロックのハッシュを再計算する必要があります。



ハッシュキャッシュ



ビットコインは、メールスパムから保護するために開発されたProof-of-WorkアルゴリズムであるHashcashを使用しています。 このアルゴリズムは、次の手順に分割できます。



  1. 既知のデータを取得します(電子メールの場合、これは受信者のアドレスです。ビットコインの場合、これはブロックのタイトルです
  2. それらにカウンターを追加します。 カウンターはゼロから始まります
  3. +



    組み合わせからハッシュを取得+



  4. ハッシュが特定の要件を満たしているかどうかを確認します
    1. はいの場合、すべての準備ができています
    2. そうでない場合は、カウンターを増やして手順3と4を繰り返します


したがって、これはブルートフォースアルゴリズムです:カウンターの変更、ハッシュの計算、チェック、カウンターの増加、ハッシュの再計算など。 そのため、アルゴリズムは計算コストが高くなります。



次に、ハッシュが満たさなければならない要件を検討します。 元のHashcashの実装では、要件は「ハッシュの最初の20ビットはゼロでなければならない」と思われます。 ビットコインでは、計画に従って、10分ごとにブロックを生成する必要があるため、要件は随時調整されます。ただし、コンピューティングパワーは時間とともに増加し、ますます多くのマイナーがネットワークに参加します。



アルゴリズムを示すために、前の例(「I like donuts」)を使用して、3つのゼロバイトで始まるハッシュを見つけます。







ca07ca



はカウンタの16進数表現で、10進数の13240266に対応します。



実装



それでは、理論を終えて、コードに取りかかりましょう。 まず、マイニングの複雑さを判断しましょう。



 const targetBits = 24
      
      





ビットコインでは、「ターゲットビット」は、ブロックがマイニングされた複雑さを格納するブロックヘッダーフィールドです。 修正アルゴリズムを構築しないため、複雑さをグローバル定数として定義します。



24は任意の数です。私たちの目標は、メモリ内で256ビット未満を占める複雑さを持つことです。 そして、差が大きいほど、正しいハッシュを見つけることが難しくなるので、差は十分に大きく、大きすぎないようにします。



 type ProofOfWork struct { block *Block target *big.Int } func NewProofOfWork(b *Block) *ProofOfWork { target := big.NewInt(1) target.Lsh(target, uint(256-targetBits)) pow := &ProofOfWork{b, target} return pow }
      
      





ここでは、ブロックへのポインターへのポインターとターゲットへのポインターを含むcreate ProofOfWork



を作成します。 「ターゲット」は、前のパートで説明した要件の別名です。 ハッシュがターゲットと比較される方法のために、 ビッグ整数を使用します。ハッシュをビッグ整数に変換し、ターゲットよりも小さいかどうかを確認します。

NewProofOfWork



関数NewProofOfWork



NewProofOfWork



big.Int



値1で初期化big.Int



それを256-targetBits



ビットにシフトします。 256



はSHA-256ハッシュのビット単位の長さであり、このハッシュアルゴリズムを使用します。 target



の16進表現:



  0x10000000000000000000000000000000000000000000000000000000000
      
      





また、メモリ内で29バイトを占有します。 そして、これは前の例のハッシュとの視覚的な比較です:



 0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3 0000010000000000000000000000000000000000000000000000000000000000 0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca
      
      





最初のハッシュ(「I like donuts」で計算)はターゲットよりも大きいため、これは有効な作業証明ではありません。 2番目のハッシュ(「I like donutsca07ca」で計算)はターゲットよりも小さいため、これは良い証拠です。



ターゲットを範囲の上限と見なすことができます。数値(ハッシュ)が境界よりも小さい場合は適切であり、逆も同様です。 境界線を下げると、適切な数字の数が減り、正しい数字を見つけるのが難しくなります。



次に、ハッシュのためのデータが必要です。 それらを準備しましょう:



 func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, pow.block.Data, IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), }, []byte{}, ) return data }
      
      





このコードは非常に単純です。 ブロックのフィールドとターゲットおよび「nonce」を単純に組み合わせます。 nonce



はHashcashの記述からのカウンターであり、暗号用語です。



したがって、すべての準備が行われます。 次に、Proof-of-Workアルゴリズムのコアを実装します。



 func (pow *ProofOfWork) Run() (int, []byte) { var hashInt big.Int var hash [32]byte nonce := 0 fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data) for nonce < maxNonce { data := pow.prepareData(nonce) hash = sha256.Sum256(data) fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) if hashInt.Cmp(pow.target) == -1 { break } else { nonce++ } } fmt.Print("\n\n") return nonce, hash[:] }
      
      





まず、変数を初期化します。 hashInt



hash



整数表現です。 nonce



はカウンターです。 次に、「無限」ループを開始します。このループは、値がmath.MaxInt64



である定数maxNonce



によって制限されます。 これは、可能性のあるnonce



オーバーフローを回避するために行われます。 PoW実装の複雑さは、カウンタオーバーフローには小さすぎますが、念のため、このようなチェックを行う方が良いでしょう。



ループでは、次のことを行います。



  1. データを準備する
  2. ハッシュ256をハッシュする
  3. ハッシュをビッグ整数に変換する
  4. 結果の整数をターゲットと比較します


前に説明したように簡単です。 これで、 Block



からSetHash



メソッドを削除し、 NewBlock



関数を変更NewBlock



ます。



 func NewBlock(data string, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} pow := NewProofOfWork(block) nonce, hash := pow.Run() block.Hash = hash[:] block.Nonce = nonce return block }
      
      





nonce



Block



プロパティとして保存されていることに気付くかもしれません。 証拠を検証するにはnonce



が必要なので、これが必要です。 Block



構造は次のようになります。



 type Block struct { Timestamp int64 Data []byte PrevBlockHash []byte Hash []byte Nonce int }
      
      





ここでプログラムを実行し、すべてが正常に機能することを確認します。



 Mining the block containing "Genesis Block" 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Mining the block containing "Send 1 BTC to Ivan" 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Mining the block containing "Send 2 more BTC to Ivan" 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe Prev. hash: Data: Genesis Block Hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Data: Send 1 BTC to Ivan Hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Data: Send 2 more BTC to Ivan Hash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe
      
      





やった! これで、各ハッシュが3つのゼロバイトで始まり、ハッシュの検索に時間がかかることがわかります。



まだやることがあります。仕事の証拠を検証できるようにしましょう。



 func (pow *ProofOfWork) Validate() bool { var hashInt big.Int data := pow.prepareData(pow.block.Nonce) hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) isValid := hashInt.Cmp(pow.target) == -1 return isValid }
      
      





これは、保存されたnonce



が必要な場所です。



すべてが正常であることを確認します。



 func main() { ... for _, block := range bc.blocks { ... pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) fmt.Println() } }
      
      





出力:



 ... Prev. hash: Data: Genesis Block Hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038 PoW: true Prev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038 Data: Send 1 BTC to Ivan Hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b PoW: true Prev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b Data: Send 2 more BTC to Ivan Hash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57a PoW: true
      
      





おわりに



ブロックチェーンは現在のアーキテクチャに一歩近づいています。ブロックを追加するには計算作業が必要なので、マイニングが可能です。 ただし、いくつかの重要な機能がまだありません。ブロックチェーンデータベースは一定ではなく、ウォレット、アドレス、トランザクションはなく、コンセンサスメカニズムもありません。 次の記事では、これらすべてを検討します。



参照資料



前編

オリジナル記事

記事のソースコード

ブロックチェーンハッシュアルゴリズム

仕事の証明

ハッシュキャッシュ



All Articles