Goのブロックチェーン。 パート5:アドレス

内容



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


エントリー



前の記事で、トランザクションの実装を開始し、その操作の原理にも精通しました:アカウントがなく、個人データ(名前、シリーズ、パスポート番号など)は不要で、ビットコインのどこにも保存されません。 しかし、それでも、トランザクションの出力の所有者(つまり、出力でブロックされたコインの所有者)としてあなたを識別するものがなければなりません。 そして、それがビットコインアドレスの目的です。 これまで、任意の文字列をアドレスとして使用していましたが、今度はビットコインでの実装方法で実際のアドレスを実装します。



このパートでは、多くのコードを変更するため、すべてを詳細に説明する理由はありません。 このページにアクセスして、以前の記事と比較したすべての変更を確認してください。



ビットコインアドレス



ビットコインアドレスの例を次に示します: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa 。 これは、おそらく中本Sに属するビットコインの最初のアドレスです。 ビットコインのアドレスは公開されています。 誰かにコインを送りたい場合は、受取人の住所を知る必要があります。 しかし、アドレスは(一意性にもかかわらず)「ウォレット」の所有者としてのあなたの識別子ではありません。 実際、そのようなアドレスは公開鍵です。 ビットコインでは、あなたの身元は、コンピューター(またはアクセスできる他の場所)に保存されている秘密キーと公開キーのペアです。 このようなキーを作成するには、暗号化アルゴリズムを使用して、キーに物理的にアクセスしない限り、誰もコインにアクセスできないようにします。 これらのアルゴリズムが何であるかを見てみましょう。



公開鍵暗号システム



公開鍵暗号システムは、公開鍵と秘密鍵のペアを使用します。 公開鍵は誰とでも共有できます。 反対に、秘密鍵は誰にも開示されるべきではありません。所有者以外の誰もアクセスするべきではありません。これらは所有者の識別子として機能する秘密鍵だからです。 あなたの顔はあなたの秘密鍵です(もちろん、暗号通貨の世界では)。



本質的に、ビットコインウォレットはそのようなキーのペアです。 ウォレットアプリケーションをインストールするか、Bitcoinクライアントを使用して新しいアドレスを作成すると、キーペアが生成されます。 秘密鍵を制御する者は、送信されたすべてのコインを制御します。



秘密鍵と公開鍵は単純にランダムなバイトシーケンスであるため、画面に印刷して人が読むことはできません。 これが、Bitcoinがアルゴリズムを使用して公開キーを人間が読み取れる文字列に変換する理由です。



Bitcoinウォレットをアプリケーションとして使用したことがある場合は、おそらくニーモニックフレーズが作成されています。 このようなフレーズは、秘密鍵の代わりに使用され、それらを生成するために使用できます。 このメカニズムはBIP-039で実装されています。



これで、ビットコインでユーザーを識別するものがわかりました。 しかし、Bitcoinはトランザクション出口(およびそこに保存されているコイン)の所有者をどのように確認しますか?



電子デジタル署名



数学と暗号化には、電子デジタル署名の概念があります-保証するアルゴリズム:



  1. 送信者から受信者への送信中にデータが変更されていないこと。
  2. データが特定の送信者によって作成されたこと。
  3. 送信者がデータを送信したことを否定できないこと。


電子デジタル署名アルゴリズムをデータに適用(つまり、データに署名)すると、後で検証できる署名が取得されます。 データは秘密鍵を使用して署名され、検証には公開鍵が必要です。



データに署名するには、次のものが必要です。



  1. 署名用のデータ。
  2. 秘密鍵。


アルゴリズムは、トランザクション入力に保存される署名を作成します。 署名を確認するには、次のものが必要です。



  1. 署名されたデータ。
  2. 署名
  3. 公開鍵。


簡単に言えば、検証プロセスは次のように説明できます。公開キーの生成に使用された秘密キーを使用して、このデータから署名が取得されることを確認する必要があります。

デジタル署名は暗号化されておらず、データを取得できません。 これはハッシュのようなものです。アルゴリズムを使用してデータを変換し、その一意の表現を取得します。 ハッシュと署名の違いは、後者を検証できるキーペアです。

ただし、このようなキーペアはデータの暗号化にも使用できます。秘密キーは暗号化に使用され、公開キーは復号化に使用されます。 ただし、ビットコインは暗号化アルゴリズムを使用しません。



ビットコインの各トランザクションエントリは、このトランザクションを作成した人によって署名されます。 ビットコインのすべてのトランザクションは、ブロックに配置する前に検証する必要があります。 検証手段(他の手順の中で):



  1. 入力に、以前のトランザクションからの出力を使用するための十分な権限があることを確認します。
  2. トランザクションの署名の検証。


概略的に、データの署名と検証のプロセスは次のようになります。







完全なトランザクションライフサイクルを見てみましょう。



  1. 当初は、コインベーストランザクションを伴うジェネシスブロックがあります。 coinbaseトランザクションには実際の入力がないため、署名は不要です。 トランザクション出力には、ハッシュされた公開キーが含まれます( RIPEMD16(SHA256(PubKey))



    アルゴリズムが使用されますRIPEMD16(SHA256(PubKey))



    )。
  2. 誰かがコインを送信すると、トランザクションが作成されます。 トランザクション入力は、以前のトランザクションの終了を参照します。 各エントリには、公開キー(ハッシュされていない)とトランザクション全体の署名が格納されます。
  3. トランザクションを受信するビットコインネットワーク内の他のノードも検証します。 とりわけ、これは、トランザクションの入力での公開キーのハッシュを対応する出力のハッシュと比較します(これにより、送信者は自分に属するコインのみを使うことが保証されます)。 署名が正しい(これにより、コインの実際の所有者によってトランザクションが作成されます)。
  4. ノードが新しいブロックをマイニングする準備ができると、ブロックにトランザクションを配置し、マイニングを開始します。
  5. ブロックがマイニングされると、ネットワーク内の他の各ノードは、ブロックがマイニングされたというメッセージを受信し、それを回線に追加します。
  6. ブロックがチェーンに追加されると、トランザクションが完了し、その出力を新しいトランザクションで参照できるようになります。


楕円暗号



すでに述べたように、公開キーと秘密キーはランダムバイトのシーケンスです。 他の誰かに属する秘密鍵を生成したくないため、シーケンスを生成するための特別なアルゴリズムが必要です。



ビットコインは楕円曲線を使用して秘密鍵を生成します。 楕円曲線は複雑な数学的概念であり、ここでは詳細に説明しません(興味がある場合は、 ここでそれについて読むことができます。 警告 :多くの数学!)。 Bitcoinが使用する曲線は、0〜2²randomlyの数字をランダムに選択できます(これは、約10 isです。注意してください、目に見える宇宙の原子は10⁷⁸〜10⁸²の間です)。 このような制限は、同じ秘密鍵を2回生成することはほとんど不可能であることを意味します。



さらに、ビットコインはトランザクションに署名するためにECDSAアルゴリズムを使用します。



ベース58



それでは、上記のアドレス1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNaに戻りましょう。 これは、これが一般に受け入れられている個人の公開キーの表現であることを知っています。 そして、デコードすると、次のようになります(16進数システムで書き込まれたバイトシーケンスのように)。



 0062E907B15CBF27D5425399EBF6F0FB50EBB88F18C29B7D93
      
      





ビットコインはBase58アルゴリズムを使用して、公開キーを人間が読み取れる形式に変換します。 このアルゴリズムは、よく知られているBase64と非常によく似ていますが、短いアルファベットを使用しています。 ホモグラフィ攻撃を避けるために、アルファベットから一部の文字が削除されています。 この点に関して、このアルファベットには次の文字が含まれていません:0(ゼロ)、O(大文字の "o")、I(大文字の "i")、l(小文字の "L")、および記号 "+"および "/ 」



公開鍵からアドレスを取得するプロセスは次のようになります。







このスキームに従って、上記で提示したキーは3つの部分に分割されます。



 Version 00 Public key hash 62E907B15CBF27D5425399EBF6F0FB50EBB88F18 Checksum C29B7D93
      
      





さて、すべてをまとめたので、次はコードを作成します。 理解できなかったことがすべて明らかになることを願っています。



アドレス実装



ウォレットのウォレット構造から始めましょう。



 type Wallet struct { PrivateKey ecdsa.PrivateKey PublicKey []byte } type Wallets struct { Wallets map[string]*Wallet } func NewWallet() *Wallet { private, public := newKeyPair() wallet := Wallet{private, public} return &wallet } func newKeyPair() (ecdsa.PrivateKey, []byte) { curve := elliptic.P256() private, err := ecdsa.GenerateKey(curve, rand.Reader) pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) return *private, pubKey }
      
      





ウォレットは、キーのペアにすぎません。 ウォレットのコレクションを保存し、ファイルに保存し、そこからアンロードするには、 Wallets



タイプも必要です。 Wallet



コンストラクターで新しいキーペアが作成されWallet



newKeyPair



関数newKeyPair



簡単です。ここではECDSAを使用します。 次に、曲線を使用して秘密鍵が作成され、秘密鍵を使用して公開鍵が生成されます。 注:楕円曲線アルゴリズムでは、公開キーは曲線上の点です。 したがって、公開キーはX、Y座標の組み合わせであり、ビットコインでは、これらの座標が組み合わされて公開キーが形成されます。



次に、アドレス生成関数を作成します。



 func (w Wallet) GetAddress() []byte { pubKeyHash := HashPubKey(w.PublicKey) versionedPayload := append([]byte{version}, pubKeyHash...) checksum := checksum(versionedPayload) fullPayload := append(versionedPayload, checksum...) address := Base58Encode(fullPayload) return address } func HashPubKey(pubKey []byte) []byte { publicSHA256 := sha256.Sum256(pubKey) RIPEMD160Hasher := ripemd160.New() _, err := RIPEMD160Hasher.Write(publicSHA256[:]) publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) return publicRIPEMD160 } func checksum(payload []byte) []byte { firstSHA := sha256.Sum256(payload) secondSHA := sha256.Sum256(firstSHA[:]) return secondSHA[:addressChecksumLen] }
      
      





公開鍵をBase58アドレスに変換する手順を見てみましょう。



  1. 公開鍵をRIPEMD160 (SHA256 (PubKey))



    し、ハッシュアルゴリズムRIPEMD160 (SHA256 (PubKey))



    を使用して2回RIPEMD160 (SHA256 (PubKey))



    ます。
  2. バージョンを準備します。
  3. ステップ2の結果とSHA256 (SHA256 (payload))



    ハッシュすることにより、チェックサムを計算します。 チェックサムは、受信したハッシュの最初の4バイトです。
  4. チェックサムをversion+PubKeyHash



    の組み合わせに追加します。
  5. 組み合わせversion+PubKeyHash+checksum



    をBase58で暗号化します。


その結果、 実際のビットコインアドレスを取得し、 blockchain.infoで残高を確認することもできます。 しかし、私はこのアドレスのアカウントには何もないことを確信しています。 そのため、適切な公開鍵暗号化アルゴリズムを選択することが非常に重要です。秘密鍵は乱数であるため、同じ番号が生成される可能性はできるだけ低くする必要があります。 理想的には、まったく繰り返さないでください。



アドレスを受信するためにビットコインノードに接続する必要がないことに注意してください。 アドレス生成アルゴリズムは、一般的なプログラミング言語の多くの標準ライブラリに既に実装されているアルゴリズムの組み合わせを使用します。



次に、アドレスを使用するために入力と出力を変更する必要があります。



 type TXInput struct { Txid []byte Vout int Signature []byte PubKey []byte } func (in *TXInput) UsesKey(pubKeyHash []byte) bool { lockingHash := HashPubKey(in.PubKey) return bytes.Compare(lockingHash, pubKeyHash) == 0 } type TXOutput struct { Value int PubKeyHash []byte } func (out *TXOutput) Lock(address []byte) { pubKeyHash := Base58Decode(address) pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4] out.PubKeyHash = pubKeyHash } func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool { return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0 }
      
      





ScriptSig



ScriptSig



は使用せScriptPubKey



、代わりにScriptSig



Signature



PubKey



PubKey



分割し、 ScriptPubKey



名前をPubKeyHash



変更しPubKeyHash



。 Bitcoinと同じように、出力のブロック/ブロック解除の機能と入力の署名のロジックを実装しますが、メソッドを使用してこれを実装します。



UsesKey



メソッドは、入力が特定のキーを使用して出力のロックを解除することを確認します。 入力はハッシュされていない公開鍵を保存し、関数はハッシュされたものを受け入れることに注意してください。 IsLockedWithKey



は、出口をブロックするために公開キーハッシュキーが使用されているかどうかを確認します。 これはUsesKey



オプション機能でUsesKey



、どちらもFindUnspentTransactions



使用されてトランザクション間の接続を構築します。



Lock



単に出口をブロックします。 コインを誰かに送るとき、アドレスしか知らないので、関数はアドレスを唯一の引数として受け取ります。 次に、アドレスがデコードされ、公開キーハッシュキーがそこから抽出されて、 PubKeyHash



フィールドに格納されます。



それでは、すべてが正しく機能することを確認しましょう。



 $ blockchain_go createwallet Your new address: 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt $ blockchain_go createwallet Your new address: 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h $ blockchain_go createwallet Your new address: 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy $ blockchain_go createblockchain -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt 0000005420fbfdafa00c093f56e033903ba43599fa7cd9df40458e373eee724d Done! $ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 10 $ blockchain_go send -from 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -to 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -amount 5 2017/09/12 13:08:56 ERROR: Not enough funds $ blockchain_go send -from 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt -to 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h -amount 6 00000019afa909094193f64ca06e9039849709f5948fbac56cae7b1b8f0ff162 Success! $ blockchain_go getbalance -address 13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt Balance of '13Uu7B1vDP4ViXqHFsWtbraM3EfQ3UkWXt': 4 $ blockchain_go getbalance -address 15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h Balance of '15pUhCbtrGh3JUx5iHnXjfpyHyTgawvG5h': 6 $ blockchain_go getbalance -address 1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy Balance of '1Lhqun1E9zZZhodiTqxfPQBcwr1CVDV2sy': 0
      
      





いいね! トランザクション署名を実装するときが来ました。



署名の実装



これはビットコインでトランザクションの信頼性を保証する唯一の方法であるため、トランザクションに署名する必要があります。 署名が無効な場合、トランザクションは無効と見なされ、したがって、チェーンに追加できません。



1つを除いて、トランザクション署名の実装用のすべてがあります。署名用のデータです。 トランザクションのどの部分に署名する必要がありますか? または、取引全体に署名する必要がありますか? 署名するデータの選択は非常に重要です。 実際、署名が必要なデータには、データを一意に識別する情報が含まれている必要があります。 たとえば、出力値のみに署名することは意味がありません。そのような署名では送信者と受信者が考慮されないためです。



トランザクションが以前の出力のブロックを解除し、それらの値を再配布し、新しい出力をブロックする場合、次のデータに署名する必要があります。



  1. ロックされていない出力に保存されている公開キーハッシュ。 これにより、トランザクションの「送信者」が識別されます。
  2. 新しいロックされたコンセントに保存されている公開キーハッシュ。 これにより、トランザクションの「受信者」が識別されます。
  3. 新しい出力の意味。


Bitcoinでは、ロック/ロック解除ロジックは、それぞれScriptSig



ScriptPubKey



入力フィールドと出力フィールドに保存されるスクリプトに保存されます。 Bitcoinはさまざまなタイプのそのようなスクリプトを許可するため、 ScriptPubKey



コンテンツ全体にScriptPubKey



ます。



これに関して、トランザクションはビットコインでは署名されませんScriptPubKey



指定された出力のScriptPubKey



含む入力を含む処理済みコピー



ここでは、トランザクションのコピーを処理するため詳細なプロセスについて説明します 。 おそらく古くなっていますが、より信頼できる情報源を見つけることができませんでした。


これはすべて十分に複雑なようです。コードを書き始めましょう。 そして、 Sign



メソッドから始めます。



 func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) { if tx.IsCoinbase() { return } txCopy := tx.TrimmedCopy() for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) signature := append(r.Bytes(), s.Bytes()...) tx.Vin[inID].Signature = signature } }
      
      





このメソッドは、秘密キーと以前のトランザクションの連想配列を受け入れます。 前述のように、トランザクションに署名するには、トランザクション入力で指定された出力にアクセスする必要があるため、これらの出力を保存するトランザクションが必要です。



このメソッドを詳しく見てみましょう。



 if tx.IsCoinbase() { return }
      
      





Coinbaseトランザクションには実際の出力がないため、署名されていません



 txCopy := tx.TrimmedCopy()
      
      





トランザクション全体ではなく、処理済みのコピーに署名します。



 func (tx *Transaction) TrimmedCopy() Transaction { var inputs []TXInput var outputs []TXOutput for _, vin := range tx.Vin { inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil}) } for _, vout := range tx.Vout { outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash}) } txCopy := Transaction{tx.ID, inputs, outputs} return txCopy }
      
      





コピーにはすべての入力と出力が含まれ、 TXInput.Signature



TXInput.PubKey



はnilになります。



次に、コピーの各エントリを確認します。



 for inID, vin := range txCopy.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
      
      





各入力で、 Signature



nil(単なるダブルチェック)に設定され、 PubKey



の出力へのリンクPubKey



割り当てられます。 現時点では、現在のトランザクションを除くすべてのトランザクションは「空」です。 PubKey



、署名PubKey



PubKey



はゼロです。 したがって、 入力は個別署名されますが 、これはアプリケーションには必要ありませんが、ビットコインでは、異なるアドレスを参照するエントリをトランザクションに含めることができます。



  txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil
      
      





Hash



メソッドは、トランザクションをシリアル化し、SHA-256アルゴリズムを使用してハッシュします。 結果は、署名の準備ができたデータです。 ハッシュを受け取った後、 PubKey



フィールドをリセットして、以降の反復に影響を与えないPubKey



する必要があります。



  r, s, err := ecdsa.Sign(rand.Reader, &privKey, txCopy.ID) signature := append(r.Bytes(), s.Bytes()...) tx.Vin[inID].Signature = signature
      
      





txCopy.ID



privKey



署名しprivKey



。 ECDSA署名は、組み合わせてSignature



入力フィールドに保存する数値のペアです。



検証機能を検討してください。



 func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool { txCopy := tx.TrimmedCopy() curve := elliptic.P256() for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil r := big.Int{} s := big.Int{} sigLen := len(vin.Signature) r.SetBytes(vin.Signature[:(sigLen / 2)]) s.SetBytes(vin.Signature[(sigLen / 2):]) x := big.Int{} y := big.Int{} keyLen := len(vin.PubKey) x.SetBytes(vin.PubKey[:(keyLen / 2)]) y.SetBytes(vin.PubKey[(keyLen / 2):]) rawPubKey := ecdsa.PublicKey{curve, &x, &y} if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { return false } } return true }
      
      





この方法は非常に簡単です。 最初に、前の方法のように、トランザクションのコピーを取得します。



 txCopy := tx.TrimmedCopy()
      
      





次に、キーペアの生成に使用される曲線が必要です。



 curve := elliptic.P256()
      
      





次に、すべての入力を調べて、それらが署名されていることを確認します。



 for inID, vin := range tx.Vin { prevTx := prevTXs[hex.EncodeToString(vin.Txid)] txCopy.Vin[inID].Signature = nil txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash txCopy.ID = txCopy.Hash() txCopy.Vin[inID].PubKey = nil
      
      





検証中に署名したデータと同じデータが必要になるため、この部分はSignメソッドで使用される部分と同じです。



  r := big.Int{} s := big.Int{} sigLen := len(vin.Signature) r.SetBytes(vin.Signature[:(sigLen / 2)]) s.SetBytes(vin.Signature[(sigLen / 2):]) x := big.Int{} y := big.Int{} keyLen := len(vin.PubKey) x.SetBytes(vin.PubKey[:(keyLen / 2)]) y.SetBytes(vin.PubKey[(keyLen / 2):])
      
      





ここでは、署名が数値のペアであり、公開キーが座標のペアであるため、 TXInput.Signature



およびTXInput.PubKey



格納されている値を解凍します。 以前はストレージ用にそれらを連結していましたが、 crypto/ecdsa



で使用するためにそれらを解凍する必要がありcrypto/ecdsa







  rawPubKey := ecdsa.PublicKey{curve, &x, &y} if ecdsa.Verify(&rawPubKey, txCopy.ID, &r, &s) == false { return false } } return true
      
      





次に、入力からecdsa.PublicKey



した公開キーを使用してecdsa.PublicKey



を作成し、入力から署名を渡してecdsa.Verify



を実行します。 すべての入力がチェックされると、trueを返します。 少なくとも1つの入力がテストに失敗した場合、falseを返します。



ここで、以前のトランザクションを取得する関数が必要です。 これにはチェーン全体との相互作用が必要なため、 Blockchain



メソッドにします。



 func (bc *Blockchain) FindTransaction(ID []byte) (Transaction, error) { bci := bc.Iterator() for { block := bci.Next() for _, tx := range block.Transactions { if bytes.Compare(tx.ID, ID) == 0 { return *tx, nil } } if len(block.PrevBlockHash) == 0 { break } } return Transaction{}, errors.New("Transaction is not found") } func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) { prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } tx.Sign(privKey, prevTXs) } func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool { prevTXs := make(map[string]Transaction) for _, vin := range tx.Vin { prevTX, err := bc.FindTransaction(vin.Txid) prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX } return tx.Verify(prevTXs) }
      
      





FindTransaction



は、識別子によってトランザクションを検索します(これには、チェーン内のすべてのブロックに対する反復が必要です)。 SignTransaction



は1つのトランザクションをSignTransaction



、それが参照するトランザクションを見つけて署名します。 VerifyTransaction



は、トランザクションを検証するだけです。



次に、トランザクションに署名して検証する必要があります。 NewUTXOTransaction



メソッドにサインインしNewUTXOTransaction







 func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { ... tx := Transaction{nil, inputs, outputs} tx.ID = tx.Hash() bc.SignTransaction(&tx, wallet.PrivateKey) return &tx }
      
      





トランザクションの検証は、ブロックに追加される前に発生します。



 func (bc *Blockchain) MineBlock(transactions []*Transaction) { var lastHash []byte for _, tx := range transactions { if bc.VerifyTransaction(tx) != true { log.Panic("ERROR: Invalid transaction") } } ... }
      
      





以上です! もう一度確認しましょう。



 $ blockchain_go createwallet Your new address: 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR $ blockchain_go createwallet Your new address: 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab $ blockchain_go createblockchain -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR 000000122348da06c19e5c513710340f4c307d884385da948a205655c6a9d008 Done! $ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 6 0000000f3dbb0ab6d56c4e4b9f7479afe8d5a5dad4d2a8823345a1a16cf3347b Success! $ blockchain_go getbalance -address 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR Balance of '1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR': 4 $ blockchain_go getbalance -address 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab Balance of '1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab': 6
      
      





何も壊していません、すごい!



bc.SignTransaction (& tx, wallet.PrivateKey)



への呼び出しをコメントアウトして、未署名のトランザクションをマイニングできないようにします。



 func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { ... tx := Transaction{nil, inputs, outputs} tx.ID = tx.Hash() // bc.SignTransaction(&tx, wallet.PrivateKey) return &tx }
      
      





 $ go install $ blockchain_go send -from 1AmVdDvvQ977oVCpUqz7zAPUEiXKrX5avR -to 1NE86r4Esjf53EL7fR86CsfTZpNN42Sfab -amount 1 2017/09/12 16:28:15 ERROR: Invalid transaction
      
      





おわりに



ビットコインのほぼすべての主要機能を実装しましたが、これは驚くべきことです。 次のパートでは、最終的にトランザクションの実装を完了します。



参照資料



  1. 完全なソースコード
  2. 公開鍵暗号
  3. デジタル署名
  4. 楕円曲線
  5. 楕円曲線暗号
  6. ECDSA
  7. ビットコインアドレスの技術的背景
  8. 住所
  9. ベース58
  10. 楕円曲線暗号の簡単な紹介



All Articles