ビットコインの概要-プロトコル

トランザクションは、ビットコインネットワークおよび他のブロックチェーンでもほとんど「主要な」オブジェクトです。 したがって、それらについて章全体を書く場合は、一般的に可能なすべてのことを伝えて示す必要があると判断しました。 特に、それらがどのように構築され、プロトコルレベルで機能するか。







以下では、トランザクションがどのように形成されるかを説明し、署名方法を示し、ノード間の通信のメカニズムを示します。







ミーム











目次



  1. キーとアドレス
  2. ノードの検索
  3. バージョンハンドシェイク
  4. 接続のセットアップ
  5. 取引をする
  6. 署名トランザクション
  7. スニッフィング&スプーフィング
  8. 送信トランザクション
  9. リンク集


キーとアドレス



まず、新しいキーペアとアドレスを作成します。 Bitcoinの概要-暗号化でこれがどのように行われるかを説明したので、ここですべてが明確になるはずです。 プロセスを高速化するためにVitalik Buterin自身 が作成したBitcoin用のこのツールセットを取り上げますが、必要に応じて既に作成されたコードフラグメントを使用できます。







$ git clone https://github.com/vbuterin/pybitcointools $ cd pybitcointools $ sudo python setup.py install $ python Python 2.7.12 (default, Jul 1 2016, 15:12:24) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from bitcoin import * >>> private_key = "28da5896199b85a7d49b0736597dd8c0d0c0293f130bf3e3e1d102e0041b1293" >>> public_key = privtopub(private_key) >>> public_key '0497e922cac2c9065a0cac998c0735d9995ff42fb6641d29300e8c0071277eb5b4e770fcc086f322339bdefef4d5b51a23d88755969d28e965dacaaa5d0d2a0e09' >>> address = pubtoaddr(public_key) >>> address '1LwPhYQi4BRBuuyWSGVeb6kPrTqpSVmoYz'
      
      





0.00012 BTCをアドレス1LwPhYQi4BRBuuyWSGVeb6kPrTqpSVmoYz



投げたので、完全なプログラムを試すことができます。







ノードの検索



一般的に言えば、 ネットワークの分散化中に他のネットワーク参加者を見つける方法について考えることは良いタスクです。 これについてはこちらで詳しく読むことができますが、完全に分散化されたソリューションはまだ存在していません。







2つの方法を示します。 最初はDNSシードです。 一番下の行は、次のような信頼できるアドレスがあることです。









これらはchainparams.cppにハードコーディングされており、 nslookup



コマンドはそれらからノードアドレスを取得できます。







 $ nslookup bitseed.xf2.org Non-authoritative answer: Name: bitseed.xf2.org Address: 76.111.96.126 Name: bitseed.xf2.org Address: 85.214.90.1 Name: bitseed.xf2.org Address: 94.226.111.26 Name: bitseed.xf2.org Address: 96.2.103.25 ...
      
      





別の方法はそれほどスマートではなく、実際には使用されませんが、教育目的ではさらに優れています。 Shodanに行き、登録し、ログインし、検索バーのport:8333



に書き込みます。 これはbitcoind



の標準ポートで、私の場合は約9,000ノードありました:







しょうだん







バージョンハンドシェイク



ノード間の接続は、2つのメッセージの交換から始まります。 バージョンメッセージが最初に送信され、 verackメッセージが応答として使用されますビットコインwikiの バージョンハンドシェイクプロセスの図を以下に示します







ローカルピアLがリモートピアRに接続する場合、リモートピアはバージョンメッセージを受信するまでデータを送信しません。

  • L-> Rローカルピアのバージョンとともにバージョンメッセージを送信する
  • R-> Lバージョンメッセージを送り返す
  • Rバージョンを2つのバージョンの最小値に設定します
  • R-> L verackメッセージを送信
  • Lバージョンを2つのバージョンの最小値に設定します


これは主に、ノードが「対話者」が使用し、1つの言語で通信できるプロトコルのバージョンを見つけるために行われます。







接続のセットアップ









ネットワーク上の各メッセージは、 magic + command + lenght + checksum + payload



の形式で提示する必要がありますmakeMessage



関数がこれを担当します。 トランザクションを送信するときにこの関数を使用します。







コードは常に構造体ライブラリを使用します。 彼女は、パラメーターを正しい形式で提示する責任があります。 たとえば、 struct.pack("q", timestamp)



は、プロトコルの要求に応じて、現在のUNIX時間をlong long int



に書き込みます。







 import time import socket import struct import random import hashlib def makeMessage(cmd, payload): magic = "F9BEB4D9".decode("hex") # Main network ID command = cmd + (12 - len(cmd)) * "\00" length = struct.pack("I", len(payload)) check = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4] return magic + command + length + check + payload def versionMessage(): version = struct.pack("i", 60002) services = struct.pack("Q", 0) timestamp = struct.pack("q", time.time()) addr_recv = struct.pack("Q", 0) addr_recv += struct.pack(">16s", "127.0.0.1") addr_recv += struct.pack(">H", 8333) addr_from = struct.pack("Q", 0) addr_from += struct.pack(">16s", "127.0.0.1") addr_from += struct.pack(">H", 8333) nonce = struct.pack("Q", random.getrandbits(64)) user_agent = struct.pack("B", 0) # Anything height = struct.pack("i", 0) # Block number, doesn't matter payload = version + services + timestamp + addr_recv + addr_from + nonce + user_agent + height return payload if __name__ == "__main__": sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(("93.170.187.9", 8333)) sock.send(makeMessage("version", versionMessage())) sock.recv(1024) # receive version message sock.recv(1024) # receive verack message
      
      





次にWiresharkを開き、 bitcoin



またはtcp.port == 8333



フィルターを設定して、結果のパッケージを確認します。 すべてが正しく行われた場合、最初に、プロトコル、 user-agentブロック開始の高さなどが正しく定義されます。 第二に、約束されたように、 バージョンverackメッセージの形式で回答を受け取ります 。 接続が確立されたので、作業を開始できます。







Wireshark







取引をする



トランザクションを作成する前に、 仕様を再度開き、慎重に仕様に従ってください。 1バイトの偏差はすでにトランザクションを無効にするため、非常に注意する必要があります。







まず、 トランザクションのアドレス、プライベートキー、およびハッシュを設定します。これを参照します。







 previous_output = "60ee91bc1563e44866c66937b141e9ef4615a272fa9d764b9468c2a673c55e01" receiver_address = "1C29gpF5MkEPrECiGtkVXwWdAmNiQ4PBMH" my_address = "1LwPhYQi4BRBuuyWSGVeb6kPrTqpSVmoYz" private_key = "28da5896199b85a7d49b0736597dd8c0d0c0293f130bf3e3e1d102e0041b1293"
      
      





次に、トランザクションを生の形式で作成します。つまり、これまでのところは署名なしです。 これを行うには、仕様に従ってください:







 def txnMessage(previous_output, receiver_address, my_address, private_key): receiver_hashed_pubkey= base58.b58decode_check(receiver_address)[1:].encode("hex") my_hashed_pubkey = base58.b58decode_check(my_address)[1:].encode("hex") # Transaction stuff version = struct.pack("<L", 1) lock_time = struct.pack("<L", 0) hash_code = struct.pack("<L", 1) # Transactions input tx_in_count = struct.pack("<B", 1) tx_in = {} tx_in["outpoint_hash"] = previous_output.decode('hex')[::-1] tx_in["outpoint_index"] = struct.pack("<L", 0) tx_in["script"] = ("76a914%s88ac" % my_hashed_pubkey).decode("hex") tx_in["script_bytes"] = struct.pack("<B", (len(tx_in["script"]))) tx_in["sequence"] = "ffffffff".decode("hex") # Transaction output tx_out_count = struct.pack("<B", 1) tx_out = {} tx_out["value"]= struct.pack("<Q", 1000) # Send 1000 satoshis tx_out["pk_script"]= ("76a914%s88ac" % receiver_hashed_pubkey).decode("hex") tx_out["pk_script_bytes"]= struct.pack("<B", (len(tx_out["pk_script"]))) tx_to_sign = (version + tx_in_count + tx_in["outpoint_hash"] + tx_in["outpoint_index"] + tx_in["script_bytes"] + tx_in["script"] + tx_in["sequence"] + tx_out_count + tx_out["value"] + tx_out["pk_script_bytes"] + tx_out["pk_script"] + lock_time + hash_code)
      
      





おそらく予想どおり、 tx_in["script"]



フィールドには<Sig> <PubKey>



と表示されないことに注意してください。 代わりに、参照するブロッキング終了スクリプトOP_DUP OP_HASH160 dab3cccc50d7ff2d1d2926ec85ca186e61aef105 OP_EQUALVERIFY OP_CHECKSIG



れます。この場合、 OP_DUP OP_HASH160 dab3cccc50d7ff2d1d2926ec85ca186e61aef105 OP_EQUALVERIFY OP_CHECKSIG



です。







ところで 、通常のOP_DUP OP_HASH160 dab3cccc50d7ff2d1d2926ec85ca186e61aef105 OP_EQUALVERIFY OP_CHECKSIG



76a914dab3cccc50d7ff2d1d2926ec85ca186e61aef105s88ac



間に違いはありません。







 0x76 = OP_DUP 0xa9 = OP_HASH160 0x14 =   14   dab3cccc50d7ff2d1d2926ec85ca186e61aef105s88ac ...
      
      





署名トランザクション



ここでトランザクションに署名します。ここではすべてが非常に簡単です。







 hashed_raw_tx = hashlib.sha256(hashlib.sha256(tx_to_sign).digest()).digest() sk = ecdsa.SigningKey.from_string(private_key.decode("hex"), curve = ecdsa.SECP256k1) vk = sk.verifying_key public_key = ('\04' + vk.to_string()).encode("hex") sign = sk.sign_digest(hashed_raw_tx, sigencode=ecdsa.util.sigencode_der)
      
      





生のトランザクションの署名取得されたら、ロック解除スクリプトを実際のスクリプトに置き換えて、トランザクションを最終的なフォームに戻すことができます。







 sigscript = sign + "\01" + struct.pack("<B", len(public_key.decode("hex"))) + public_key.decode("hex") real_tx = (version + tx_in_count + tx_in["outpoint_hash"] + tx_in["outpoint_index"] + struct.pack("<B", (len(sigscript) + 1)) + struct.pack("<B", len(sign) + 1) + sigscript + tx_in["sequence"] + tx_out_count + tx_out["value"] + tx_out["pk_script_bytes"] + tx_out["pk_script"] + lock_time) return real_tx
      
      





スニッフィング&スプーフィング



ここで詳細を明確にする必要があります。 私たちが一般的にトランザクションに署名する理由を理解していると思います。 これは、メッセージの署名が変更されるなどの理由で、誰もメッセージを変更してネットワーク上で送信できないようにするためです。







しかし、注意深く読んでいると、最終的に他のノードに送信される偽のトランザクションに署名していることを覚えているでしょう、そしてその修正は、ロック解除スクリプトが参照する出力からのロックスクリプトを示します。 原則として、これが起こる理由は理解できます:このロック解除スクリプトでは、この署名を記述する必要があり、悪循環が得られます:正しい署名には正しいロック解除スクリプトが必要です。正しいロック解除スクリプトには正しい署名が必要です。 それで、サトシは妥協し、まったく「本物」ではない署名の使用を許可しました。







したがって、ネットワーク上の誰かがメッセージをキャッチし、ロック解除スクリプトを変更し、編集したメッセージをさらに送信する可能性があります。 署名はロック解除スクリプトを「保護」しないため、どのノードもこれを確認できません。 この脆弱性は、 トランザクションの順応性と呼ばれます。詳細についてはこちらを参照するか、Black Hat USA 2014- Bitcoin Transaction Malleability Theory in Practiceのレポートを参照してください。







TL; DR P2PKHなどの標準スクリプトを使用する場合、危険はありません。 それ以外の場合は注意が必要です。







送信トランザクション



バージョンメッセージの場合と同じ方法で、トランザクションがネットワークに送信されます







 if __name__ == "__main__": sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(("70.68.73.137", 8333)) sock.send(makeMessage("version", versionMessage())) sock.recv(1024) # version sock.recv(1024) # verack # Transaction options previous_output = "60ee91bc1563e44866c66937b141e9ef4615a272fa9d764b9468c2a673c55e01" receiver_address = "1C29gpF5MkEPrECiGtkVXwWdAmNiQ4PBMH" my_address = "1LwPhYQi4BRBuuyWSGVeb6kPrTqpSVmoYz" private_key = "28da5896199b85a7d49b0736597dd8c0d0c0293f130bf3e3e1d102e0041b1293" txn = txnMessage(previous_output, receiver_address, my_address, private_key) print "Signed txn:", txn sock.send(makeMessage("tx", txn)) sock.recv(1024)
      
      





結果のコードを実行し、実行してパッケージを調べます。 すべてが正しく行われている場合、 invメッセージメッセージへの応答として送信されます (そうでない場合、 拒否メッセージになります )。 興味深い事実は、各ノードが新しいトランザクションを受信すると、その有効性を確認することです(プロセスはBitcoin in a nutshell-Miningで説明されています)。したがって、どこかでミスをすると、すぐに通知されます:







成功







トランザクションがネットワークに送信されてから数秒以内に追跡できます 、最初は未確認としてリストされます。 その後、しばらくして(最大数時間)、トランザクションがブロックに含まれます。







その時点までにWireshark plusを閉じずにバージョンメッセージで現在のブロックチェーンの高さを示している場合、同じinvメッセージの形式で新しいブロックに関する通知を受け取りますが、今回はTYPE = MSG_BLOCK



(私はそれを閉じたので、以下のブログのスクリーンショットですケン・シリフ ):







msg_block







Data hash



は、長い行が表示されます。これは、実際にはリトルエンディアン形式の新しいブロックのタイトルです。 この場合、これは0000000000000001a27b1d6eb8c405410398ece796e742da3b3e35363c2219eeという見出しのブロック#279068です。 多数の先行ゼロは偶然ではなく、マイニングの結果です。これについては別に説明します。







しかし、その前に、ブロックチェーン自体、ブロック、それらのヘッダーなどに対処する必要があります。 したがって、次の章: ビットコインの概要-ブロックチェーン












All Articles