見出し画像

【BTC】UTXOを技術的に詳しく。

BTCの仕組みを理解しようとすると絶対に出てくるUTXO(Unspent Transaction Output)という存在。これは一体なんでしょう?

その概要的な答えは、以下の記事で書かれています。

この記事では、UTXOというものがどのような仕組みでどのように動いているのかという奥深くまで知る機会として活用していただければと思います。

簡単に残高から。

ビットコインのウォレットにある残高はどのようにして算出されるのでしょうか。これまでの取引記録から見たそのウォレットが使用できるトランザクションの合算値として出されます。


トランザクションから残高を見る

上図は過去にトランザクションが1つしか発生していない時の残高の見方です。(前提としてBobは0BTCの残高でなにも取引していない状態です。)では、何回か取引をしてみましょう。2回取引をした場合は以下のようになります。

(少々正確な言葉ではありませんが、)AliceからBobに送られた時には、1つのトランザクションが存在しました。そのあと、BobからCathyに送金した時には二つのトランザクションができます。この時にはBobの残高は0.9BTCです。

ではここで新たに、MikeからBobに1.4BTCが送られてきたとしましょう。

この時、元々使用できるようになっていた0.9BTCとMikeから送られてきた1.4BTCの合計となる2.3BTCとなります。では、ここから、Bobが2BTCをAliceに送金することとします。簡単に考えると、残高は2.3BTCだから2BTC送ると残高は0.3BTCになります。その時の算出方法は下図のようになります。


いつの間にか、UTXOの仕組みに触れていたあなた

ここまで見てきた時に、
1回目の送金のトランザクション(取引)である

[Alice→Bob 1BTC]

はCathyに送る時に使用され、2つのトランザクションとして作成されます。また、4回目の送金の時には、

[Bob→Bob 0.9BTC] & [Mike→Bob 1.4BTC]

がAliceに2BTC送る時に使用されています。このように残高としてみれて、まだ使用されていないトランザクションのことをUTXO(Unspent Transaction Output)と呼ばれます。日本語でいうと「未使用トランザクションアウトプット」とも言われます。(普通にUTXOで使われますが…)

これをoutputとつかわれてるくらいなのでinputがあるはずです。そこでinputとoutputの組み合わせを考えてみます。赤い点線での枠がビットコインで作成されるトランザクション単位になります。


実際のデータをみてみる

ランダムにエクスプローラーから取り出してみてみる

bitcoinのエクスプローラーで、ランダムにトランザクションをとってみてみましょう。FromとToに着目してみてください。

このトランザクションの情報をJSONの部分をみてみると、、、

{
  "txid": "0119bbd345616f3c96ee19a3f37fa3650ec2d1396235ede36516c52fd99e489e",
  "size": 370,
  "version": 1,
  "locktime": 0,
  "fee": 1780,
  "inputs": [
    {
      "coinbase": false,
      "txid": "37e2a66cef2963e090db688634b54fabac87e41b8bdf982c57d200dc912dc079",
      "output": 3,
      "sigscript": "",
      "sequence": 4294967295,
      "pkscript": "0014722cba18f0582af0926d7432694d0ffd1fc888e8",
      "value": 76000,
      "address": "bc1qwgkt5x8stq40pyndwsexjng0l50u3z8gk4ma8p",
      "witness": [
        "304402204121c37b54f3d1924eb67341b6cffa0f25f40a7ed2c4f500930dfb6cfb17fb8702206c2c757e7e54665f7d06f3d80a3edd250da22e73e7183ddb35fd910c978c733d01",
        "035de5cf90f2191680031731406012972b910c59c46beede76a59c0877ac38d803"
      ]
    },
    {
      "coinbase": false,
      "txid": "1e3ca50f32361bee35bd53c39f07cc87eb856f4b20a76e4f838cb46754ba748e",
      "output": 0,
      "sigscript": "",
      "sequence": 4294967295,
      "pkscript": "00142927a136d297eef1509a8002d6381e0bad9bd06f",
      "value": 43949967,
      "address": "bc1q9yn6zdkjjlh0z5y6sqpdvwq7pwkeh5r0ka28ad",
      "witness": [
        "304402202f0e07fe4d105f73778aa96fd858d6f178128b96a062cefdd3e1cc42da8c283a0220226c13ab6ac6a0c0deafb441f41f92de81e01099d4372888d5a93f3ca4f4552c01",
        "02cf7ff74a9cd3ae162a1ca96a54c3c265a3b81ce4fc4d0195ea3885be2ec6012c"
      ]
    }
  ],
  "outputs": [
    {
      "address": "bc1q9yn6zdkjjlh0z5y6sqpdvwq7pwkeh5r0ka28ad",
      "pkscript": "00142927a136d297eef1509a8002d6381e0bad9bd06f",
      "value": 43804887,
      "spent": false,
      "spender": null
    },
    {
      "address": "bc1q82mcvq66c4qa5374mm4myrd80hmcxl4dg62guq",
      "pkscript": "00143ab786035ac541da47d5deebb20da77df7837ead",
      "value": 219300,
      "spent": false,
      "spender": null
    }
  ],
  "block": {
    "mempool": 1724984456
  },
  "deleted": false,
  "time": 1724984456,
  "rbf": false,
  "weight": 832
}
Explore top crypto assets.

inputsとしている中に、2つのトランザクション(txid)があります。

txid: 37e2a66cef2963e090db688634b54fabac87e41b8bdf982c…→ value: 76000 Satoshi = 0.00076 BTC

txid: 1e3ca50f32361bee35bd53c39f07cc87eb856f4b20a76…
→ value: value: 43949967 Satoshi = 0.43949967 BTC

この二つのUTXOを使って、outputsで払いたい 43804887 Satoshiと手数料 1780 Satoshi を払うことができます。

※ Satoshiは "0.00000001 BTC = 1 Satoshi" という単位のこと


二重支払い攻撃(二重支払い問題)

二重支払い問題はデジタルトランザクションにおいて、同じ資産を複数の取引に使おうとすることによって生じるものです。ブロックチェーンでは、分散化されたネットワークで信頼性を確保する必要が出てきます。

上記までの内容を文言でまとめると、

UTXOが特定の金額を保持し、そのUTXOを使って新しいトランザクションを作成することができ、そのときに使用されたUTXOは消費(使用済みと)され、新しいUTXOが生成される。

となります。

もし同じUTXOを二度使用しようとした場合、ネットワーク側でもうすでに使用されたUTXOを検知し、トランザクションを無効とします。これにより、二重支払いを防ぐことができます。

二重支払い問題の解決

二重支払い問題の解決には以下のような要素が挙げられます。

  • 唯一性と透明性:
    同じUTXOが複数のトランザクションに使用されることは許容されないため、ブロックチェーン上での全ての取引は透明かつ一意に識別されるため、重複を防ぐことができます。

  • 分散化と信頼性:
    中央集権的な管理者が不在のブロックチェーンでは、各ノードが同じルールに従ってトランザクションを検証します。これにより、信頼できる取引の整合性を保ちながら、二重支払いを防ぐことができます。

  • 過去のトランザクションの参照:
    UTXOモデルでは、どのUTXOが使用されたかを明確に追跡できます。過去のトランザクションの履歴を参照することで、二重支払いを発見し、適切に対処することが可能です。


【コラム】コードを実行してみよう

PythonであるアドレスのUTXOを取り出してみる

import requests

def get_utxo(address):
    # endpoint
    url = f'https://blockstream.info/api/address/{address}/utxo'
    # send
    response = requests.get(url)

    # response
    if response.status_code == 200:
        utxos = response.json()
        # jsonデータをみたい場合には、コメントアウトをはずす
        # print(utxos)
        return utxos
    else:
        print(f"Error: Unable to retrieve UTXOs. Status code: {response.status_code}")
        return None

# enter a bitcoin address
bitcoin_address = 'your_bitcoin_address_here'

# get UTXOs
utxos = get_utxo(bitcoin_address)

# show UTXOs
if utxos is not None:
    print(f"UTXOs for address {bitcoin_address}:")
    for utxo in utxos:
        print(f"TXID: {utxo['txid']}, Output Index: {utxo['vout']}, Value: {utxo['value']} BTC")
else:
    print("No UTXOs found or error retrieving data.")
 

bitcoinの送金処理をPythonで書いてみる

from bitcoinlib.wallets import Wallet
from bitcoinlib.keys import Key
from bitcoinlib.transactions import Transaction
from bitcoinlib.services import Service

# 設定
private_key_wif = 'your_private_key_here'  # 秘密鍵(WIF形式)
recipient_address = '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'  # 送金先のアドレス
amount_to_send = 0.001  # 送金するビットコインの量(BTC単位)を指定
fee = 0.0001  # 取引手数料(BTC単位)を指定

# キーのロード
key = Key().from_wif(private_key_wif)
address = key.address

# ウォレットの読み込み
wallet = Wallet.create(name='temp_wallet', keys=key) if not Wallet.exists('temp_wallet') else Wallet('temp_wallet')

# UTXOの取得
utxos = wallet.utxos()
total_available = sum(utxo['value'] for utxo in utxos)

if total_available < amount_to_send + fee:
    raise ValueError("Insufficient funds")

# UTXOを選択
selected_utxos = []
amount_accumulated = 0
for utxo in utxos:
    selected_utxos.append(utxo)
    amount_accumulated += utxo['value']
    if amount_accumulated >= amount_to_send + fee:
        break

# トランザクションの作成
tx = Transaction()
for utxo in selected_utxos:
    tx.add_input(utxo['txid'], utxo['output_n'], utxo['value'])
tx.add_output(recipient_address, amount_to_send)
change_amount = amount_accumulated - (amount_to_send + fee)
if change_amount > 0:
    tx.add_output(address, change_amount)

# トランザクションの署名
tx.sign(key)

# トランザクションの送信
service = Service()
txid = service.sendrawtransaction(tx.raw_hex())
print(f"Transaction ID: {txid}")

この記事が気に入ったらサポートをしてみませんか?