P2PKH https://techgrowup.net エンジニアを強くする Sat, 15 Mar 2025 23:00:00 +0000 ja hourly 1 https://wordpress.org/?v=6.7.2 https://techgrowup.net/wp-content/uploads/2021/05/hp-icon-150x150.png P2PKH https://techgrowup.net 32 32 初心者にもわかるビットコイン・トランザクションの仕組み:構造・スクリプト・検証プロセスを解説 https://techgrowup.net/blockchain-transaction/ https://techgrowup.net/blockchain-transaction/?noamp=mobile#respond Sat, 15 Mar 2025 23:00:00 +0000 https://techgrowup.net/?p=2662 はじめに

暗号資産(暗号通貨)の代表格であるビットコインでは、送金や受け取りなどの取引データを「トランザクション」と呼びます。このトランザクションこそが、ビットコインのブロックチェーンを支える基盤的な仕組みです。
本記事では、ビットコイン・トランザクションの構造とその働き、スクリプトや署名の仕組み、さらに検証の流れや手数料などについて、大体 5000 文字を目安にわかりやすく解説していきます。ビットコインをはじめとした暗号資産の基盤となる仕組みに興味がある方は、ぜひ最後までご覧ください。

ビットコイン・トランザクションとは?

基本的な役割

ビットコインの「トランザクション(Transaction)」は、あるアドレスから別のアドレスへビットコインを送金する際の情報をまとめたものです。銀行の振り込みに例えれば「振込依頼書」のようなもので、そこにはどこから送金するか(入力)と、どこへ送金するか(出力)の情報が詰まっています。

UTXO(未使用トランザクション出力)

ビットコインでは「残高」という概念を直接持たず、「未使用トランザクション出力(Unspent Transaction Output、UTXO)」という形で各アドレスの資産を管理します。簡単に言えば、過去に受け取った出力(まだ使われていないもの)を合わせた総量が、そのアドレスが使えるビットコインの量です。

  • 送金時は、手持ちのUTXOのうち必要な分を入力(Input)として参照
  • お釣りを出力(Output)として自分のアドレスに戻すことも多い

この UTXO モデルによって、ビットコインは効率的かつ高い安全性で残高管理を行っています。

トランザクションの永続化

トランザクションの情報は「ブロック」にまとめられてチェーン状に連結されます。マイナー(採掘者)によってProof of Workが行われ、ブロックが承認されると、そのブロックに含まれるトランザクションが正式に「記録」される仕組みです。これを繰り返すことでブロックチェーンが成長し、過去のすべての取引履歴が保全されます。

トランザクションの構造

ビットコインのトランザクションには、以下のようなフィールド(要素)が含まれます。主なものを挙げると:

  1. バージョン(version)
    トランザクション形式のバージョン番号。将来的なアップグレードに対応するために利用される。
  2. 入力数(tx_in count)
    トランザクションが参照する UTXO の数。
  3. 入力(tx_in)
    それぞれの入力は下記情報を含む:
    • 前のトランザクションID(どのUTXOを使うか)
    • 出力インデックス(前のトランザクションのどの出力か)
    • スクリプト署名(scriptSig):検証のための署名や公開鍵情報が入る
    • シーケンス番号(sequence):Locktimeと連動して利用される場合がある
  4. 出力数(tx_out count)
    ビットコインをどのように割り振るかを示す出力の数。1 つのトランザクションに複数の出力を持たせることが可能。
  5. 出力(tx_out)
    それぞれの出力は下記情報を含む:
    • 金額(value):Satoshi 単位(1 BTC = 100,000,000 Satoshi)
    • スクリプト公開鍵(scriptPubKey):支払いを受け取るアドレスを規定するスクリプト
  6. Locktime
    トランザクションが有効となる時間やブロック高さを指定できる。0 が大半だが、一部で時間ロックや CSV(CheckSequenceVerify)等に利用されることがある。

以下は、非常に簡略化したトランザクション構造のイメージ図です。実際のバイナリデータはもっと細かく分かれていますが、概念を掴むうえでの参考にしてください。

┌───────────────────────┐
│ version (4 bytes)     │
├───────────────────────┤
input count (VarInt)  │
├───────────────────────┤
│ tx_in[0]              │
│   ├ prev_tx_id        │
│   ├ prev_out_index    │
│   ├ scriptSig (VarInt)│
│   └ sequence (4 bytes)│
├───────────────────────┤
│   ... (more tx_in) ...│
├───────────────────────┤
output count (VarInt) │
├───────────────────────┤
│ tx_out[0]             │
│   ├ value (8 bytes)   │
│   └ scriptPubKey (VarInt│
│       ... script data...)│
├───────────────────────┤
│   ... (more tx_out) ..│
├───────────────────────┤
│ locktime (4 bytes)    │
└───────────────────────┘

ビットコインのスクリプト言語

スクリプトの基本

ビットコインのトランザクションでは、scriptSig(入力側)とscriptPubKey(出力側)が組み合わさって実行されます。これが「支払い条件の成立」を検証する重要な仕組みです。

  • scriptSig: 送金者が署名などのデータを含む
  • scriptPubKey: 受取アドレスや条件式を記述している

この 2 つがスタックマシン上で順次実行され、「条件が満たされれば送金が有効」と判断されます。

P2PKH(Pay to Public Key Hash)

ビットコインで最も一般的なのが P2PKH 形式(1 から始まるビットコインアドレス)です。

  • scriptPubKey には、受取人の公開鍵ハッシュと OP_EQUALVERIFY OP_CHECKSIG などの命令が含まれる
  • scriptSig には、送金者の署名(signature)と公開鍵(public key)が含まれる

実行時に、公開鍵が scriptPubKey のハッシュと一致し、署名検証をパスすれば「正当な所有者」とみなされます。

Segregated Witness(SegWit)対応

SegWit(セグウィット)導入後の P2WPKH(bc1 から始まるアドレス)や P2WSH は、署名データをメインのトランザクションデータから切り離し、witness と呼ばれる領域に格納します。これによりブロック容量の利用効率が向上し、手数料の計算などが少し変わります。

トランザクションの検証プロセス

トランザクションがネットワークにブロードキャストされると、各ノードは以下のようなステップで検証を行います。

  1. 基本的な構文チェック
    • version, locktime, tx_in, tx_out の形式が正しいか
    • 入力数や出力数が 0 件ではないか
    • 合計出力金額が 0 より大きく、ビットコインの上限を超えていないか
  2. UTXO の参照チェック
    • 入力で参照している UTXO が実際に存在し、未使用であるか
    • ダブルスペンド(同じ UTXO を複数回使う不正)になっていないか
  3. スクリプトの実行
    • scriptSig と scriptPubKey を連結し、スタックマシンで実行
    • 署名検証や公開鍵ハッシュの一致、OP_CODE の実行結果などをチェック
  4. 手数料とアウトプットの合計チェック
    • 入力の合計(UTXO) – 出力の合計(新たなトランザクション出力) = 手数料が正しいか
    • 負の値になっていないか
  5. プールへの受け入れ
    • 上記をすべて満たしたトランザクションは、ノードのメモリプール(mempool)に格納され、マイナーがブロックに取り込む対象となる。

手数料の仕組み

トランザクションのバイトサイズ

ビットコインでは、トランザクション手数料は「送金額」ではなく、トランザクションのバイトサイズ(正確には weight 単位)によって大きく影響されます。

  • 入力数や出力数が多いと、トランザクションのサイズが大きくなり、手数料も高くなる
  • SegWit を使うと witness データの扱いが変わり、手数料を若干抑えやすい場合がある

手数料率(Fee Rate)

手数料は「Satoshi/byte」や「Satoshi/vbyte」などのレートとして表されることが多いです。例えば、1 vbyte あたり 10 satoshi の手数料を支払う設定にしておけば、200 バイトのトランザクションなら 2000 satoshi(0.00002 BTC)程度となります。
マイナーは手数料の高いトランザクションを優先してブロックに含める傾向にあるため、迅速な承認を望む場合は高めの手数料を設定するのが一般的です。

ロックタイムとシーケンス

Locktime(nLocktime)

Locktime フィールドを使うことで、トランザクションが承認される「最も早い時刻(またはブロック高さ)」を指定できます。デフォルトで 0 が多いですが、特定のブロック番号や UNIX 時刻を設定すると、その条件に達するまではトランザクションが有効にならない仕組みです。
この機能を活用して、時間ロック付きの支払いや、オフチェーンでの契約など、さまざまなユースケースが考えられます。

シーケンス番号(sequence)

各入力ごとに設定される 4 バイトの値で、Locktime を有効化するかどうかなどの条件をコントロールできます。また、BIP68 などで定義される相対ロックタイムの仕組みを使う場合にも関係します。

Python でトランザクションをパースする例

ここでは、ビットコインのトランザクションをパース(読み込んで要素を抽出)する簡単な Python スニペットを示します。実際には生のバイナリデータをデコードする必要がありますが、ここでは概念を掴むイメージとしてご覧ください。

import binascii
import struct

# この Raw トランザクションは実際にビットコイン・メインネットで
# 確認可能なものの一例です(1インプット / 2アウトプットなど)。
# すべて16進で、行分割せず1行で記載しているため偶数長になっています。
raw_tx = "01000000017b1eabe0209b1fe794124575ef807057c77ada2138ae4fa8d6c4de0398a14f3f00000000494830450221008949f0cb400094ad2b5eb399d59d01c14d73d8fe6e96df1a7150deb388ab8935022079656090d7f6bac4c9a94e0aad311a4268e082a725f8aeae0573fb12ff866a5f01ffffffff01f0ca052a010000001976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac00000000".strip()

def parse_varint(data: bytes, offset: int):
    """
    ビットコインで扱われる可変長整数(VarInt)を読み取り、
    値と次のオフセットを返す簡易関数です。
    """
    prefix = data[offset]
    offset += 1

    if prefix < 0xfd:
        return prefix, offset
    elif prefix == 0xfd:
        val = struct.unpack_from('<H', data, offset)[0]
        offset += 2
        return val, offset
    elif prefix == 0xfe:
        val = struct.unpack_from('<I', data, offset)[0]
        offset += 4
        return val, offset
    else:
        val = struct.unpack_from('<Q', data, offset)[0]
        offset += 8
        return val, offset

def main():
    # 1) 16進文字列をバイナリに変換
    #    ここで文字数が偶数長であることが重要。
    binary_tx = binascii.unhexlify(raw_tx)

    offset = 0

    # 2) まず 4 バイトの version を読む
    version = struct.unpack_from('<I', binary_tx, offset)[0]
    offset += 4
    print(f"Version: {version}")

    # 3) 入力数 (VarInt)
    tx_in_count, offset = parse_varint(binary_tx, offset)
    print(f"Input Count: {tx_in_count}")

    # 4) 入力 (tx_in) をパース
    for i in range(tx_in_count):
        # 先行する32バイトで prev_txid(逆順で格納されているので[::-1]で反転)
        prev_txid = binary_tx[offset:offset+32][::-1]
        offset += 32

        # その後4バイトで 出力インデックス (vout)
        out_index = struct.unpack_from('<I', binary_tx, offset)[0]
        offset += 4

        # scriptSig の長さ (VarInt)
        script_len, offset = parse_varint(binary_tx, offset)
        # scriptSig 本体
        script_sig = binary_tx[offset:offset+script_len]
        offset += script_len

        # 4バイトの sequence
        sequence = struct.unpack_from('<I', binary_tx, offset)[0]
        offset += 4

        print(f"--- Input {i} ---")
        print(f"Prev TXID: {binascii.hexlify(prev_txid).decode()}")
        print(f"OutIndex: {out_index}")
        print(f"ScriptSig: {binascii.hexlify(script_sig).decode()}")
        print(f"Sequence: 0x{sequence:x}")

    # 5) 出力数 (VarInt)
    tx_out_count, offset = parse_varint(binary_tx, offset)
    print(f"Output Count: {tx_out_count}")

    # 6) 出力 (tx_out) をパース
    for j in range(tx_out_count):
        # 8バイトの value (satoshi)
        value = struct.unpack_from('<Q', binary_tx, offset)[0]
        offset += 8

        # scriptPubKey の長さ (VarInt)
        script_len, offset = parse_varint(binary_tx, offset)
        # scriptPubKey 本体
        script_pubkey = binary_tx[offset:offset+script_len]
        offset += script_len

        print(f"--- Output {j} ---")
        print(f"Value (satoshi): {value}")
        print(f"ScriptPubKey: {binascii.hexlify(script_pubkey).decode()}")

    # 7) 最後に locktime (4バイト)
    locktime = struct.unpack_from('<I', binary_tx, offset)[0]
    offset += 4
    print(f"Locktime: {locktime}")

if __name__ == "__main__":
    main()
  • parse_varint は可変長整数(VarInt)を処理する関数の例
  • 実際には scriptSig や scriptPubKey を解析し、署名や公開鍵ハッシュを取り出す必要がある
  • ビットコインでのバイナリデータのフォーマットに沿って正しく解析するのは、やや手間がかかりますが、詳細な仕組みを理解する良い勉強になります

出力例:

Version: 1
Input Count: 1
--- Input 0 ---
Prev TXID: 3f4fa19803dec4d6a84fae3821da7ac7577080ef75451294e71f9b20e0ab1e7b
OutIndex: 0
ScriptSig: 4830450221008949f0cb400094ad2b5eb399d59d01c14d73d8fe6e96df1a7150deb388ab8935022079656090d7f6bac4c9a94e0aad311a4268e082a725f8aeae0573fb12ff866a5f01
Sequence: 0xffffffff
Output Count: 1
--- Output 0 ---
Value (satoshi): 4999990000
ScriptPubKey: 76a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac
Locktime: 0

今後のトランザクションの拡張

ビットコインの開発コミュニティでは、Taproot(シュノア署名等を活用したスクリプト拡張)など、さらなる機能拡張が進められています。Taproot は 2021 年に有効化され、プライバシー強化や柔軟なスクリプト表現が可能となりました。

  • シュノア署名: 署名サイズが小さくなり、複数署名(multisig)を一つの署名にまとめられる
  • Taproot でのスクリプト隠蔽: 実際に使われなかったスクリプトパスを公開しなくても良くなる

こうした拡張によって、ビットコインはスケーラビリティだけでなく、スクリプトの表現力やプライバシーも向上し、より多彩なユースケースに対応できるようになっています。

まとめ

ビットコインのトランザクションは、UTXO モデルをベースに組み立てられ、scriptSigscriptPubKey が連動して検証される仕組みが特徴です。こうした構造によって高いセキュリティと分散性が保たれ、世界中のノードがトランザクションを検証・記録することで、信頼性の高い分散型台帳が実現されています。

  • UTXO モデルで残高管理
  • スクリプト言語による柔軟な支払い条件
  • 手数料はトランザクションサイズによる
  • Locktime や sequence で高度な支払いロックを設定可能
  • Taproot 等の進化によって、より複雑かつ安全なスクリプトが実現へ

ブロックチェーンやビットコインに興味を持たれた方は、まずトランザクションの構造と仕組みを学ぶことが近道です。実際にコードを書いて raw トランザクションを解析してみると、暗号通貨の基礎理解が一層深まることでしょう。
今後もビットコインはプロトコルの更新やユーザーコミュニティの拡大によって進化を続けていくと考えられます。トランザクション構造をしっかり理解しておけば、その応用や拡張にもスムーズに対応できるはずです。ぜひ本記事を入り口として、ビットコイン・トランザクションの世界をさらに掘り下げて学んでみてください。

]]>
https://techgrowup.net/blockchain-transaction/feed/ 0