Develop with pleasure!

福岡でCloudとかBlockchainとか。

Bitcoin Cashのトランザクションを作成してBCHを入手してみた

8/1のBitcoin CashのハードフォークでBitcoinがBTCとBCHに分岐した。分岐前にBTCを保持していると、それと同額のBCHも持っていることになる。

取引所などに預けていて、取引所がBCHに対応していれば何もしなくても取引所が同額のBCHが付与してくれる。モバイルウォレットなどでBTCを保管していた場合は、それぞれのウォレットがBitcoin Cashに対応していればBCHを入手できる。ウォレットがBCHに対応していない場合にBCHを入手するには以下のような方法が考えられる。

  • BTCとBCHのウォレットが共にHDウォレットで、BIP-39のようなワードリストからマスターシードを復元できる共通の仕様をサポートしていれば、BTCのウォレットのワードリストをBCHのウォレットにインポートすることでBCHを入手できる。
  • BTCを保持しているアドレスの秘密鍵をBCHのウォレットにインポートする。
  • BTCを保持しているアドレスの秘密鍵を使ってBCHのトランザクションを作成してネットワークにブロードキャストする。

なんとなくウォレットのマスターシードを移行するのは気が引けたのと、Bitcoin Cashトランザクションを作ってみたかったので最後の方法でBCHを入手してみた。

秘密鍵の入手

まず8/1以前からBTCを保持しているアドレスの秘密鍵を導出する。

ウォレットはmyceliumを使っていて、myceliumはBIP-32、BIP-39、BIP-44をサポートしているウォレットなので、ワードリストからマスターシードを復元し、階層的に鍵を導出することで、別のプログラムからでもmyceliumアプリ内で使用している鍵を導出することができる。

BIP-44に対応しているので以下のパス階層で鍵導出をする。

m / purpose' / coin_type' / account' / change / address_index
  • purpose'
    BIP-44なので指定するのは44の強化鍵 = 231 + 44 = 0x8000002C
  • coin_type'
    Bitcoinなので0の強化鍵 = 231 + 0 = 0x80000000
  • account'
    ウォレットで使ってるアカウントのインデックス(0から始まる)の強化鍵 = = 231 + 0 = 0x80000000
  • change
    0が外部からBitcoinを受け取るためのアドレス、1が自分がBitcoinを送る際のおつり用のアドレスを示す定数。これは強化鍵ではないのでそのままの値を使用する(0)。
  • address_index
    0から始まるアドレスのインデックス。これも強化鍵ではないのでそのままの値を使用する。

以下、開発中のbitcoinrb(v0.1.3)を使ってmyceliumで使用している鍵を導出するコード↓

require 'bitcoin'

mnemonic = Bitcoin::Mnemonic.new('english')
    
# ワードリストからシードを復元
seed = mnemonic.to_seed(%w(abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about))
    
# シードからマスター拡張秘密鍵を生成
master = Bitcoin::ExtKey.generate_master(seed)
    
# マスター拡張秘密鍵から1階層目の拡張秘密鍵を導出
level1 = master.derive(0x8000002C)
# 1階層目の拡張秘密鍵からcoin typeを指定して2階層目の拡張秘密鍵を導出
level2 = level1.derive(0x80000000)
# 2階層目の拡張秘密鍵からaccountを指定して3階層目の拡張秘密鍵を導出
level3 = level2.derive(2**31 + 0)
# 3階層目の拡張秘密鍵からchangeを指定して4階層目の拡張秘密鍵を導出(おつり用の場合は1を指定)
level4 = level3.derive(0)
  
# 4階層目の拡張秘密鍵から決済に使用している秘密鍵を導出(とりあえず10個ほど導出)
10.times do |index|
  key = level4.derive(index)
  puts key.addr # アドレス
  puts key.priv # 秘密鍵
end

myceliumで使用されているアドレスが実際に導出できていたら成功。

このまま秘密鍵Bitcoin Cashのノードにインポートするのも可。

Bitcoin CashのSignature Hashの生成方法

Bitcoin Cashではリプレイ保護のため、Signature Hashの生成方法がBitcoinとは変わっている。

bitcoin-abc/replay-protected-sighash.md at master · Bitcoin-ABC/bitcoin-abc · GitHub

Bitcoinではall(1)none(2)single(3)というSIGHASHタイプと、anyonecanpay(0x80)フラグを組み合わせた合計6個のSIGHASHタイプが定義されているが、Bitcoin Cashにはforkid(0x40)というフラグが新たに追加された。

Bitcoin Cashトランザクションに署名する際はこのforkid(0x40)を組み合わせる。このとき署名対象のメッセージダイジェストの生成方法はBIP-143の仕様に従う。

techmedia-think.hatenablog.com

Bitcoin Cashトランザクションの作成

↑のforkid(0x40)を使ってBitcoin Cashを送付するトランザクションをbitcoinrbで作ってみる↓
(bitcoinrb自体は今のところBitcoin Cashに対応はしてないけど、Cash用のトランザクション自体は以下のようにすれば作れる。)

require 'bitcoin'

SIGHASH_FORKID = 0x40

# ロックされているUTXOの秘密鍵
key = Bitcoin::Key.new(priv_key: '秘密鍵')

# UTXOを持つトランザクション
prev_tx = Bitcoin::Tx.parse_from_payload('UTXOのトランザクションのペイロード'.htb)

# 署名対象のTxを作る
tx = Bitcoin::Tx.new
out_pint = Bitcoin::OutPoint.new(prev_tx.txid, 0)
tx_in = Bitcoin::TxIn.new(out_point: out_pint)
tx.inputs << tx_in
# 送付先 
script_pubkey = Bitcoin::Script.to_p2pkh('送付先の公開鍵ハッシュ')
tx.outputs << Bitcoin::TxOut.new(value: 送金するsatoshiの量, script_pubkey: script_pubkey)

# SIGHASH ALLにFORK_IDフラグを付与
hash_type = Bitcoin::SIGHASH_TYPE[:all] | SIGHASH_FORKID

# BIP-143準拠のメッセージダイジェストを生成
sighash = tx.sighash_for_input(0, prev_tx.outputs[0].script_pubkey,
                         hash_type: hash_type, sig_version: :witness_v0, amount: prev_tx.outputs[0].value)
# 署名生成(P2PKHの署名)
sig = key.sign(sighash) + [hash_type].pack('C')
tx_in.script_sig = Bitcoin::Script.parse_from_payload(
        Bitcoin::Script.pack_pushdata(sig) + Bitcoin::Script.pack_pushdata(key.pubkey.htb))

puts tx.to_payload.bth

出力されたペイロードsendrawtransactionとかブロードキャスト系のサービスでネットワークにブロードキャストし、トランザクションがブロックに入れられればBCHを入手できる。

BIP-32の鍵導出と組み合わせてPay-to-Contractのアドレス導出方法を定義したBIP-175

以前ブログでも記事書いたPay-to-Contractのホワイトペーパー

techmedia-think.hatenablog.com

プロトコルがBIPになった↓

https://github.com/bitcoin/bips/blob/master/bip-0175.mediawiki

動機

Bitcoinトランザクションは、二者間で価値を移転する現実世界の契約を表す。現実の取引では、請求書や領収書で支払いを追跡している。支払人が請求明細の支払いに同意し、受取人が領収書にサインすると受取人により商品が発送される。GerhardtとHankeは準同型の支払アドレスとマルチパーティのpay-to-contract protocolを使ってBitcoinプロトコル内でこの相互作用を定式化した。

このプロトコルは、誰が何に支払うのかについて暗号的な証拠を全ての当事者が保持するよう構成されている。このBIPで説明されている技術を使うと、契約の条件と受取人の公開鍵からアドレスを導出できる。この導出スキームは、UTXOを増大させることなくネットワークの参加者から隠すことができる。導出されるアドレスはP2(W)PKHやP2(W)SHアドレスのように見える。資金を償還する際は契約と受取人の秘密鍵の知識が必要になる。

このスキームではBIP-32の基盤を利用して、既存のウォレット開発者が仕様を実装するための一貫性のある方法を提供する。

仕様

この鍵導出スキームには、支払人(顧客)と受取人(マーチャント)という2人の当事者が登場する。顧客は購入したい商品/サービスを指定して、購入リクエストをマーチャントに送る。購入リクエストを受け取ったマーチャントは請求明細と総支払額を指定して請求書(コントラクト)を作成する。マーチャントはこのコントラクトと一緒にpayment baseと呼ばれる拡張公開鍵を顧客に提供する。この情報が与えられると顧客は、コントラクト及びpayment baseに関連する支払アドレスの公開鍵を生成し、そこに送金することで契約を履行する。

以下のBIP-32のパスを定義する:

m / purpose' / coin_type' / contract_hash

contract_hashは複数のレベルで構成される。

パスのアポストロフィ(‘)は、BIP-32強化導出が適用されていることを意味する。

また以下の拡張公開鍵を定義する。

payment_base :

m / purpose' / coin_type'

payment_address :

m / purpose' / coin_type' / contract_hash
もしくは
m / payment_base / contract_hash

各レベルには以下で説明する特別な意味がある。

Purpose

BIP-42の勧告に従いpurposeフィールドの定数は175'(or 0x800000AF)になる。このノードサブツリーはこの仕様に従うことを意味する。

m / 175' / *

このレベルでは強化導出が使われる。

Coin Type

coin typeフィールドは、BIP-44と同じ。

このレベルでも強化導出が使われる。

支払先アドレスの生成

コントラクトドキュメント c1,…cn、拡張公開鍵payment_base、暗号学的ハッシュ関数hが与えられたとして、以下のステップで支払先のはドレスを計算する。

  1. 全コントラクトドキュメントについてハッシュ関数を適用し、ハッシュ値を計算する。
    h(c1),...,h(cn)
  2. 全てのハッシュを辞書順にソートする。
    hash_1,...,hash_n
  3. ソートしたハッシュを結合し、さらにハッシュ関数を適用してハッシュ値を計算する。
    h(hash_1+...+hash_n)
  4. ステップ3で結合したハッシュから後述するPartial Derivation Path Mappingで定義されている部分的なBIP-32導出パスを計算する。
    contract_hash
  5. contract_hash導出パスの前にpayment_baseを追加する。
    payment_base / contract_hash
  6. ステップ5の導出パスから拡張公開鍵を計算する。
  7. ステップ6の拡張公開鍵のアドレス(P2PKH)を計算する。

支払先アドレスの検証

与えられたBitcoinアドレスと、payment_base 拡張公開鍵、 c1,…cnで表されるコントラクトドキュメント、及びhで表される暗号学的ハッシュ関数から、次のステップでアドレスの完全性を検証することができる。

  1. 支払先アドレスの生成セクションの入力から、コントラクトアドレスを計算する。
  2. ステップ1で計算したアドレスと与えられたアドレスを検証する。

償還

マーチャントは、支払先アドレスの生成セクションで記載されている方法を使って、オフラインで秘密鍵を構成することができる。マーチャントは支払先アドレス宛ての送金をブロックチェーン上で監視する。アドレスはpayment baseとコントラクトから生成されるため、ロックされた資金を使用するためには暗黙的にコントラクトの条件に同意する必要がある。そのアドレスへの支払いは、顧客にとっては領収書になる。

ハッシュからPartial Derivation Path Mapping

このセクションでは、任意の16進数の数値からBIP-32の導出パスを部分的に生成するマッピング手順について説明する。

与えられた16進数の数値について以下の操作をする。

  1. 16進数の値を4文字毎に分割する。
  2. 分割した各パートを10進数の整数に変換する。
  3. 全ての数値を/で連結する。

サンプル

以下のデータが与えられた場合

master private extended key:
xprv9s21ZrQH143K2JF8RafpqtKiTbsbaxEeUaMnNHsm5o6wCW3z8ySyH4UxFVSfZ8n7ESu7fgir8imbZKLYVBxFPND1pniTZ81vKfd45EHKX73
coin type:
0

以下のようにpayment baseを計算することができる

payment base derivation path:
m/175'/0'
contract base public extended key:
xpub6B3JSEWjqm5GgfzcjPwBixxLPzi15pFM3jq4E4yCzXXUFS5MFdXiSdw7b5dbdPGHuc7c1V4zXbbFRtc9G1njMUt9ZvMdGVGYQSQsurD6HAW

以下の例では、SHA256を暗号学的ハッシュ関数として使用し、上記のコントラクトベース公開鍵を使用している。

支払先アドレスの生成 :

入力として、2つのドキュメントで構成されるコントラクトがあり、その内容が以下の場合

document 1:
bar
document 2:
foo

1.それぞれのハッシュ値を計算する。

document 1:
fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9
document 2:
2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae

2.生成したハッシュを辞書順にソートする

2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae
fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9

3.ハッシュを連結し、そのハッシュ値を計算する。

concatenated hash:
2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7aefcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9
combined hash:
ec321de56af3b66fb49e89cfe346562388af387db689165d6f662a3950286a57

4.計算したハッシュから部分的なBIP-32導出パスを計算する。

60466/7653/27379/46703/46238/35279/58182/22051/34991/14461/46729/5725/28518/10809/20520/27223

5.contract_hash導出パスの前にpayment_baseを付与する。

contract_base_pub/60466/7653/27379/46703/46238/35279/58182/22051/34991/14461/46729/5725/28518/10809/20520/27223
or
m/175'/0'/60466/7653/27379/46703/46238/35279/58182/22051/34991/14461/46729/5725/28518/10809/20520/27223

6.公開拡張鍵を計算する。

xpub6hML7vSU2Hwww9ctwrwt4ijnhJr4n6XaMRox1nnF3PvZKuF1SZoTymnKQHrF9fp2nWJSqv5ZjJSxJJQ8A3PKcBUWhGvTFmuRKpycSCr5coz

7.公開拡張鍵からアドレス(P2PKH)を計算する。

1HYjhPTtMmpBJBd5tVepZDAVdvPA7o8KHJ

検証例1(失敗パターン) :

上記の入力を使って、今度は以下の内容の1つのドキュメントで構成されるコントラクトを使って検証する。

document 1:
baz

1.ハッシュを計算する。

baa5a0964d3320fbc0c6a922140453c8513ea24ab8fd0577034804a967248096

2.要素は1つだけだが、2回めのハッシュ計算をする。

3a08605829413ce0bf551b08d21e4a28dbda6e407f90eff1c448e839050c73a1

3.部分導出パスを計算する。

14856/24664/10561/15584/48981/6920/53790/18984/56282/28224/32656/61425/50248/59449/1292/29601

4.contract_hash導出パスの前にcontract_basepubを付与する。

contract_base_pub/14856/24664/10561/15584/48981/6920/53790/18984/56282/28224/32656/61425/50248/59449/1292/29601
or
m/175'/0'/14856/24664/10561/15584/48981/6920/53790/18984/56282/28224/32656/61425/50248/59449/1292/29601

5.拡張公開鍵を計算する。

xpub6gujKWRhegHXKZBkrprW55oSL6UxYhStxF5FtoUNa4KShLxLPDLQTS39XAwRhdCSvuAv2wogwukmfk3fS7CM6pT6QWwJHiCTw7RkwXMgThy

6.拡張公開鍵からアドレス(P2PKH)を計算する。

162KDdRXa3KPgYkH3d1DDKfddacH1gn1n8

7.予想通り生成したアドレスは、前に計算した値162KDdRXa3KPgYkH3d1DDKfddacH1gn1n8とは異なる。

検証は、コントラクトアドレス生成の際に使用したドキュメントと同じドキュメントを使用した場合のみ成功する。

互換性

この仕様はBIP-32の仕様と後方互換性はなく、このBIPの導出スキームはBIP-32に準拠している。支払人と受取人の間の通信は、コントラクトのハッシュ化やパスの生成と同様、ウォレットを大きく変更する必要がある。

参照実装

所感

  • Pay-to-contractの何をBIPで定義するのかと思ったけど、BIP-32の鍵導出の仕組みと組み合わせて決定論的に導出する仕様を定義してるのか。
  • ホワイトペーパーでは単純にコントラクトのハッシュをベースとなる鍵に直接加算して支払先のアドレスを生成してるのに対して、このBIPではコントラクトのハッシュを分割してBIP-32のパスの一部を生成し、そこからBIP-32で鍵導出してる。

部分的に署名されたトランザクション(PSBT)のフォーマットを定義するBIP-174

通常のP2PKHやP2WPKHで決済をする分には特に関係ないが、P2SHベースのマルチシグやコントラクトなどで複数のユーザーによる署名が必要な場合、ユーザー間でトランザクションをやりとりしながら、各自署名を付与し、最終的にブロードキャスト可能なトランザクションを作成する必要がある。

この部分はブロックチェーン外で行われるけど、どういうフォーマットで途中のトランザクションをやり取りするかなどは、ウォレットやサービスによってそれぞれ異なる。標準仕様がないため、当然ウォレットが違えば互換性が無いのがあたり前だが、そういう部分的に署名されたトランザクションのフォーマットを標準定義しようというのが最近追加されたBIP-174↓

https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki

イントロダクション

概要

このBIPでは、署名者がトランザクションの署名を生成するのに必要な情報と、入力が完全な署名を持たない間の中間状態の署名データ保持するバイナリトランザクションフォーマットを提案している。トランザクションの署名に必要な全ての情報が提供されるため、署名者はオフラインでも可。

動機

未署名のトランザクションや部分的に署名されたトランザクションを他の複数の署名者に渡すために作成する場合、その方法は現在それぞれの実装に依存しており、異なるウォレット間でそういったトランザクションの交換をすることは難しい。このBIPの1つのゴールは、クライアント間で同じトランザクションをやりとりし、その過程でそれぞれ署名を行うための標準的で拡張可能なフォーマットを作成することである。このフォーマットは、将来のために簡単に拡張できるよう設計されている(既存のトランザクションフォーマットでそれを行うのは難しい)。

通常、トランザクションに署名する際はユーザーは使用するUTXOへアクセスできる必要がある。このトランザクションフォーマットでは、エアギャップウォレットやハードウェアウォレットなどが直接UTXOセットにアクセスすることなく、かつ不正行為のリスクを伴わない形でトランザクションへのオフライン署名を可能にする。

仕様

部分的に署名されたBitcoinトランザクション(Partially Signed Bitcoin Transaction = PSBT)フォーマットは、key-valueのマップで構成される。各keyとvalueのペアは、スコープ内で一意でなければならず、重複は許可されない。各マップはシーケンシャルなレコードで構成され、0x00バイトで終了する。レコードのフォーマットは以下の通り。

注意:<..>は、データの長さを表すCompact Size Unsigned Integerが先頭に付与されていることを示す。{..}はRAWデータを示す。

<key>|<value>
名称 タイプ 定義
Key Length Compact Size Unsigned Integer Keyの長さ
Key byte key自体。先頭のバイトがkey-valueペアのタイプになる。
Value Length Compact Size Unsigned Integer Valueの長さ
Value byte value自体

各key-valueマップのフォーマットは以下の通り。

{key-value pair}|{key-value pair}|...|{0x00}
サイズ 名称 タイプ 定義
1+ Key-valueペア key-valueペアの配列 任意 key-valueのペア
1 セパレータ char 0x00 必ず0x00

各keyの先頭バイトには、そのkey-valueペアのタイプを指定する。いくつかのタイプはグローバルフィールド用で、他のフィールドは各入力用。PSBTで唯一必須のタイプは、以下に定義されているTransactionタイプ。

現在定義されているグローバルタイプは以下の通り。

数値(タイプ) 名称 Keyデータ Valueデータ サンプル
0x00 Transaction 無し。keyは1バイトのタイプのみ。 ネットワークのシリアライゼーションフォーマットでシリアライズされたトランザクション。各入力のscriptSigとwitnessは完全なデータでなければ空でなければならない。トランザクションはBIP-144で定義されているwitnessなシリアライゼーションフォーマットであること。PSBTにはトランザクションが必要で、それ以外の場合は無効。 Key: {0x00}, Value: {transaction}
0x01 Redeem Script hash160されるredeem script Pay-To-Script-Hashの入力に署名するために必要なredeem script、もしくは出力で使われてるredeem script。 Key: {0x01}, Value: {redeem script}
0x02 Witness Script sha256されるwitness script Pay-To-Witness-Script-Hashの入力に署名するのに必要なwitness script、もしくは出力で使われてるwitness script。 Key: {0x02}, Value: {witness script}
0x03 BIP 32の導出パスと、公開鍵とマスター鍵のfingerprint 公開鍵 master key fingerprintは公開鍵の導出パスと連結されている。導出パスは互いに連結された32 bitの符号なし整数として表される。マスター鍵のインデックスは省略しなければならない。 Key: {0x03}, Value: {マスター鍵のfingerprint}|{32-bit int}|...|{32-bit int}
0x04 入力の数 無し。keyは1バイトのタイプのみ。 トランザクションが持つ入力の数を示すcompact size unsigned integer Key: {0x04}, Value: {入力の数}

現在定義されている入力単位のタイプは以下の通り。

数値(タイプ) 名称 Keyデータ Valueデータ サンプル
0x00 非Witness UTXO 無し。keyは1バイトのタイプのみ。 入力が参照するネットワークのシリアライゼーションフォーマットでシリアライズされたトランザクション Key: {0x00}, Value: {transaction}
0x01 Witness UTXO 無し。keyは1バイトのタイプのみ。 入力が参照するネットワークのシリアライゼーションフォーマットでシリアライズされたトランザクション出力。 Key: {0x01}, Value: {serialized transaction output({output value}|<scriptPubKey>)}
`0x02 部分的な署名 公開鍵と対応する署名 scriptSigもしくはwitnessからスタックにプッシュされる署名 Key: {0x02}|{公開鍵}, Value: {署名}
0x03 Sighash Type 無し。keyは1バイトのタイプのみ。 この入力に使用することを推奨するsighash typeで32 bitの符号なし整数。sighash typeはあくまで推奨で、署名者は必ずしも指定されたsighash typeを使用する必要はない。 Key: {0x03}, Value: {sighash type}
`0x04 入力のインデックス 無し。keyは1バイトのタイプのみ。このフィールドは完了した入力をスキップできるようにするためのオプション。入力の1つでもこのタイプがある場合、全入力にこのタイプが必要になる。 この入力のインデックスを示すcompact size unsigned integer。 Key: {0x04}, Value: {入力のインデックス}

トランザクションのフォーマットは以下のように指定される。

{0x70736274}|{0xff}|{global key-value map}|{input key-value map}|...|{input key-value map}
サイズ 名称 タイプ 定義
4 マジックバイト int32_t 0x70736274 psbtのASCII文字であるマジックバイト。この整数は最上位バイト順にシリアライズする必要がある。
1 セパレータ char 0xff 必ず0xff
1+ グローバルデータ key-valueのマップ 任意 グローバルデータのkey-valueマップ
1+ 入力 key-valueマップの配列 任意 以下で説明する各入力のkey-valueペア

セパレータ間の各データブロックはスコープとみなすことができる。タイプは必要なければスキップできる。例えばwitnessな入力の場合、Non-Witness UTXOのkey-valueペアは必要無い。

署名者が理解できないkey-valueペアを検出した場合、署名者はトランザクションを再度シリアライズする場合、そのkey-valueのペアをセットしておく必要がある。

Keyが重複した場合の取り扱い

各スコープのKeyは重複しないようにすること。フォーマット内の全Keyは一意である必要がある。ただ実装者はKeyが重複していた場合にそれに対処する必要がある。Keyが重複している場合、ソフトウェアはどのvalueを使用するか選択することができる。

使い方

このトランザクションフォーマットを使用する際は、以下のようなロールが考えられる。ロールは1つのエンティティに結合されているが、ロールにはそれぞれ以下のような特定の機能がある。

Transaction Creator

トランザクションの作成者は、ネットワークのシリアライゼーションフォーマットでシリアライズしたトランザクションかPSBTのいずれかを受け入れ、PSBTを生成するか提供されたPSBTを更新する。scriptSigがまだファイナライズされていない場合は、トランザクションのそのscriptSigの部分は空にし、scriptSigのデータは入力フィールドにセットしなければならない。可能であれば、必要なredeem scriptとwitness scriptを探して、それらをPSBTのグローバルデータセクションに追加する必要がある。トランザクション作成者は、知っているUTXOのデータも記入する必要がある。

Transaction Signer

トランザクションの署名者はPSBTを受け入れ、PSBTで提供されたUTXOを使って、署名可能な入力の署名を生成する。署名に必要な情報は全てPSBT自体で提供されるため、署名者はUTXOセットへアクセスする必要はない。生成された署名は、対応する入力の部分的な署名のkey-valueペアのフィールドに追加する。

また、署名者は使用される金額、取引される金額、手数料、支払先のアドレスを計算することができる。これらのことについて全てインタラクティブにユーザーに問題ないか確認することができる。

Transaction Combiner

トランザクションのコンバイナは複数のPSBTを受け入れ、可能であれば各PSBTをパースし1つのトランザクションにマージする。複数のPSBTが同じトランザクションに対応する場合、コンバイナはそれらの全てのkey-valueペアを含む1つのPSBTを作成する必要がある。また重複したキーがあれば仕様に従って重複を除去する。

Transaction Finalizer

トランザクションのファイナライザーは、PSBTを取得し、各入力について最終的なscriptSigを構築する。入力に十分な署名が含まれている場合、ファイナライザーはそれらの署名と必要なredeem script及びwitness scriptを取得し、完全なscriptSigを構築する。作成したscriptSigはトランザクションに含める必要があり、scriptSigを作成した入力についてはその全てのkey-valueペアを削除する。また、他の入力には不要なグローバルデータを削除することがある。

Transaction Broadcaster

トランザクションブロードキャスターは、ファイナライズされたPSBTを受け取り、トランザクションを取り出し、その有効性を検証し、Bitcoinネットワークにブロードキャストする。

拡張性

PSBTフォーマットは、将来、新しいkey-valueタイプを追加することで拡張することができる。新しいタイプについて知らない署名者にとっては新しいタイプはパススルーされるだけなので、後方互換性は引き続き維持される。

さらに、異なるタイプkey-valueペアを持つkey-valueマップをフォーマットの最後に追加することができる。続く各マップの数については、パーサーがデータタイプの異なる定義をいつ使用するか知るためグローバルセクションで指定する必要がある。

互換性

このトランザクションフォーマットは、通常のトランザクションアンシリアライザーにはたらしくシリアライズされないよう設計されている。通常のトランザクションがPSBTフォーマットのトランザクションアンシリアライザーでアンシリアライズできないのと同じように。

サンプル

CoinJoinワークフローの手順

https://github.com/bitcoin/bips/raw/master/bip-0174/coinjoin-workflow.png

2-of-3マルチシグのワークフロー

https://github.com/bitcoin/bips/raw/master/bip-0174/multisig-workflow.png

論拠

  1. セパレータが0xffでなく0x00なのはなぜ?
    このセパレータはデータの各チャンクを区別するために使われる。0x00セパレータであれば、アンシリアライズが長さ0のkeyとして読み取ることを意味する。セパレータとして使用でき、シリアライザの実装を簡単にすることができる。
  2. なぜredees scriptとwitness scriptはグローバルなのか?
    重複を避けるためグローバルデータにしている。これらのスクリプトを必要とする複数の入力で、redeem scriptとwitness scriptをそれぞれ指定するのではなく、グローバルデータで1つだけ指定する。
  3. なぜ出力のredeem scriptとwitness scriptは含まれているのか?
    トランザクション出力のredeem scriptとwitness scriptが含まれていると、署名しているトランザクションが正しいredeem scriptやwitness scriptを持つ正しい出力を持つトランザクションであることを確認することができる。PSBTの作者が署名者に信頼されていない場合に適してる。
  4. なぜpsbtに4バイト使うのか?
    トランザクションフォーマットは一意に識別するため5バイトのヘッダから始まる必要があった。先頭のバイトはPartially Signed Bitcoin Transactionの略語であるpsbtのASCIIとして選択した。
  5. マジックバイトの後ろにセパレータを使う理由は?
    セパレータはPSBTの5バイトヘッダの一部だ。非PSBTのアンシリアライザーが通常のトランザクションとしてPSBTをアンシリアライズできないようにするための、0xffをセパレータにしている。5バイトのヘッダは固定されているので、非PSBTフォーマットのトランザクションがPSBTのシリアライザーによってアンシリアライズできないのと同様。
  6. なぜ全てのセパレータが必要なのか?
    セパレータはアンシリアライザーがどの入力がシリアライズされていないか知るために必要。

参照実装

github.com

所感

  • マルチシグやペイメントチャネルを構築する際のトランザクションなどは、実際には複数のユーザーでの署名が必要で、署名前のトランザクションをどう管理するかについては標準的な決まりは無いので、BIPでその標準仕様が決めるのは大事。
  • 入力のキーにUTXOが参照するトランザクションが定義されているのは、オフラインデバイスでも署名できるようにするためっぽい。
  • アプリケーションレイヤーのBIPでコンセンサスに関与するものではないので、基本的にこの仕様を採用するかどうかはアプリケーション次第。
  • トランザクションフォーマットのセパレータが0xffなのは、BIP-144のフォーマットであればちょうどマーカー(値は0)に該当して、パースしようとするとその違いからエラーになるってことかな。

未使用なUTXOかチェックするメッセージを定義したBIP-64

ルノードのUTXOセット内にある出力が存在するかチェックするためのP2Pメッセージを定義したBIP-64についてみてみる↓

https://github.com/bitcoin/bips/blob/master/bip-0064.mediawiki

※ マイクハーンによる提案で、Bitcoin Coreにはservice bitの定義は存在するけど、機能自体は実装されていない。

動機

Bitcoinの全フルノードは、未使用のトランザクション出力のセットを維持しており、このデータセットを使ってコインの二重使用をチェックしている。未使用の出力を使用する有効なトランザクションの場合、その入力の未使用出力の識別子OutPointが、このデータセット内にある。使用済みの出力を再度使用するトランザクションの場合、このデータセットの中に対象のOutPointがなく、無効と判断される。

完全なUTXOデータを保持しない軽量/SPVクライアントなどが、このフルノードのデータセットにアクセスできると役に立つ。例えば、保証契約などを実装するアプリケーションでは、新しい契約が出てきた際に、その契約が既に二重使用によって取り消されているかどうかすぐにテストして判断できるようになる。こういったアプリケーションはブロックチェーンのデータを全てダウンロードすれば実装できるので絶対必要な機能ではないが、実装してもパフォーマンスは許容範囲で、さまざまなUIケースを解決するのに便利だ。

このデータが有用なもう1つのケースは、SPVウォレット内で変動手数料の計算を行うケースだが、このケースではBitconプロトコルにいくつかの変更が必要なため、ここでは説明しない。

仕様

新しく以下のデータで構成されるgetutxosというメッセージを定義する。

バイト数 定義 タイプ コメント
1 check mempool bool 計算中のmempool内のトランザクションを適用するかどうか。
? outpoints vector 照会するOutPointのリスト。各OutPointのデータはtxメッセージの場合と同じ方法でシリアライズされる。

このメッセージの応答メッセージが以下のデータで構成されるutxosメッセージ。

バイト数 定義 タイプ コメント
4 chain height uint32 対象のUTXOが存在するかチェックした際のチェーンのHeight
32 chain tip hash uint256 対象のUTXOが存在するかチェックした際のブロックハッシュ
? hit bitmap byte 照会された各OutpointがUTXOセット内で見つかったかどうかを示すビット列。
? result utxos resutl resultオブジェクト(後述)。bitmapにbitがセットされている未使用の各OutPoint毎にresultオブジェクトが存在する。

↑のresultオブジェクトの構造は以下の通り↓

バイト数 定義 タイプ コメント
4 tx version uint32 検出されたUTXOを持つトランザクションのバージョン
4 height uint32 検出されたトランザクションが含まれるブロックのHeight。もし対象のトランザクションがmempoolにある場合は0x7FFFFFFF
? output CTxOut 出力自身で、txメッセージと同じ方法でシリアライズされている。

後方互換

ノードは70003より上のプロトコルバージョンを宣言し、nServiceフィールドに2(フィールドの2番目のビット)をセットした新しいNODE_GETUTXOフラグを設定することで、このメッセージをサポートすることを示す。

認証

チェック結果のデータは今のところ、それが確実な結果であることを認証されたデータではない。ブロックでUTXOセットのルートハッシュにコミットする新しいコンセンサスルールを導入することでこれを解決する提案があるが、この機能は現在のBitcoinプロトコルでは利用できない。もし実装されるとutxosメッセージには、コミットされたUTXOセットに含まれていることを示すMerkleブランチを含むようアップグレードできる。

このメッセージを要求するクライアントが、ローカルに保持している署名付きトランザクションの出力を探している場合、クライアントはそのトランザクションの入力と、このメッセージで帰ってきた出力を使ってスクリプトの検証ができる。現状はスクリプトが正しいか検証できるだけだが、将来のBitcoinプロトコルでは、この方法で値をチェックすることも可能になる。しかし、出力が実際に使用されてないか、ブロックチェーンで実際に作られたものかは明らかにならない。さらに、提供されたscriptPubkeyの形式を実行前にチェックし、リモートピアがスクリプトOP_TRUEをセットしてないか確認する必要がある。

メッセージを要求したクライアントが、(getheadersメッセージなどを介して)ベストチェーンのHeightとブロックハッシュのマッピングを持っていれば、追加でそのブロックデータを要求し、ブロック内の出力を検索するkとで、ある時点でその出力が存在した証拠を入手することができる。Bloom Filterと組み合わせると合理的で効率的になる。

注意:このプロトコルはBloom Filterと同じセキュリティモデルがある。リモートノードは要求されたUTXOが存在しない or 既に使用されている嘘をつくことができる。BitcoinP2Pネットワークには認証機能は無いので、ネットワークの参加者が中間者攻撃のように間違った結果を返すことも考えられるため、同じデータを複数のノードに照会して結果を検証する必要がある。

実装

github.com

支払いの証明(Proof of Payment)について定義したBIP-120

Bitcoin決済をしたことを証明する支払いの証明(Proof of Payment)方法について定義しているのがBIP-120↓

https://github.com/bitcoin/bips/blob/master/bip-0120.mediawiki

Bitcoinによる支払いが行われたことを条件にサービスなどを提供するケースって結構あるんじゃないかなー。

(BIP-120自体はコンセンサスなどとは無関係なApplicationsレイヤーのBIPなので、デプロイなどは無い。)

動機

支払いの証明(Proof of Paymant = PoP)を利用することで以下のようなユースケースが考えられる。

  • ホテルなどで既に料金を支払っている場合に、そのPoPがドアの鍵になる。
  • オンラインのビデオサービスでビデオの代金を支払っていれば、どのデバイスでも視聴できる。
  • 事前に支払い済みの広告。広告の掲載期間内であればいつでも、PoPを使って新しいコンテンツを広告としてアップロードできる。
  • PoPを使って有料サイトにログインする。
  • 毎月支払いをする駐車場や車の認証をPoPを使って行う。
  • 全参加者が同じアドレスに支払いをする宝くじで、勝者はそのアドレスへ支払いをしたトランザクションの中から選択される。勝者はそのトランザクションのPoPと賞金を交換する。

Proof of Paymentを使うと個人情報(ユーザー名やパスワード、メールアドレス)などを必要とせず、こういったユースケースを実現できる。

論拠

PoPを実現する上で必要な特性:

  1. PoPは必要に応じて生成する。
  2. 盗難リスクを避けるため基本的に一度しか使用できない。
  3. (P2SHやP2PKHなどの)スクリプトタイプに関係なく、支払いのためをPoPを作成できる必要がある。
  4. 実証済みのトランザクションの全入力のロックを解除するのに、十分な資格を持っていることを証明する必要がある。
  5. 簡単に採用できるようウォレットとサーバーの実装は簡単でなければならない。

支払いを証明するための現在の方法:

  • BIP-70では、リクエストを満たすトランザクションとともに、PaymentRequestが何らかの証明をするが、1,2もしくは4は満たしておらず、BIP-70では3しかできない。そのため証明を要求/提供する標準的な方法は定義されていない。標準化すれば5を満たせる。
  • サーバーが生成したメッセージに、トランザクションに署名するのに使う秘密鍵を使って署名する。これは1と2を満たすかもしれないが、おそらく3は満たせない。これも標準化はされていない。4は設計されていれば満たせる。

仕様

データ構造

トランザクションTの支払いに対する証明(PoP(T)とする)は、Tの全ての入力をアンロックするのに必要な情報を所有していることを証明するデータでもある。PoP(T)の入力は基本的にTと同じトランザクション構造(同じ入力を同じ順で持つ)をもつデータで、入力データのうちシーケンス番号だけは元と違い0がセットされている。またpop outputと呼ばれるコインの量が0の出力を1つ持つ。このpop outputの形式は↓

OP_RETURN <version> <txid> <nonce>
フィールド バイト数 内容
version 2 バージョン。リトルエンディアンで現在は0x01 0x00
txid 32 証明するトランザクションのtxid
nonce 6 ランダムデータ

PoPのロックタイムは必ず499999999にセットする必要がある。これは誤ってPoPがBitcoinP2Pネットワークに流れても、ブロックに取り込まれることが無いようにするため。シーケンス番号がffffffffだとロックタイムを無効にするので、シーケンス番号に0をセットしているのもそのため。この仕様では全入力のシーケンス番号を0にするようにしている。ロックタイムを有効にするのには1つでも十分だけど、シンプルにするため。

TとPoP(T)の構造はそれぞれ以下のようになる。

  T
 +------------------------------------------------+
 |inputs                | outputs                 |
 |       Value,Sequence | Value,Script            |
 +------------------------------------------------+
 |input0 1,ffffffff     | 0,pay to A              |
 |input1 3,ffffffff     | 2,OP_RETURN <some data> |
 |input2 4,ffffffff     | 1,pay to B              |
 |                      | 4,pay to C              |
 +------------------------------------------------+
 
  PoP(T)
 +-------------------------------------------------------------+
 | inputs               | outputs                              |
 |       Value,Sequence | Value,Script                         |
 +-------------------------------------------------------------+
 |input0 1,00000000     | 0,OP_RETURN <version> <txid> <nonce> |
 |input1 3,00000000     |                                      |
 |input2 4,00000000     |                                      |
 +-------------------------------------------------------------+
 | lock_time=499999999                                         |
 +-------------------------------------------------------------+

PoPはBitcoinトランザクションと同じ署名プロセスで署名される。

nonceは盗まれたPoPの使用を困難にするための要素で、サーバーがPoPのリクエスト毎に新しいnonceを生成することで、盗まれたPoPを無効化する。

プロセス

  1. 最初にProof of Paymentのリクエストがサーバーからウォレットに送られる。このPoPリクエストには、以下が含まれる。
    1. ランダムなnonce
    2. PoPの送信先httpsなURLなど)
    3. どのトランザクションのproofを作ればいいのか示すウォレットへのヒントで、例えば
      • (サーバーが知っていれば)txid
      • (BIP-70の支払いの場合は)PaymentRequest.PaymentDetails.merchant_data
      • BIP-21のURIから得られる量、ラベル、メッセージやその他の情報
  2. ウォレットはサーバーの情報からトランザクションを特定する。ウォレット単体で特定できない場合は、1-3のヒントをユーザーに確認し、一致するものを選択してもらう。
  3. ウォレットは特定したトランザクションの未署名のUPoPを作成し、ユーザーに署名するか尋ねる。
  4. ユーザーは署名するか確認する
  5. ウォレットはUPoPに署名しPoPを作成する
  6. PoPを1の宛先に送信する
  7. サーバーは受信したPoPを検証し、その結果をvalidinvalidで返す
  8. ウォレットは何らかの方法でユーザーにレスポンスを表示する。

備考

  • ステップ1のPoPリクエストを送信する方法についてはここでは指定しない。BIP-121を参照。
  • nonceは新しいPoPリクエストごとにサーバによってランダムに生成される必要がある。

PoPの検証

サーバーはPoPを検証し、validinvalidを返す必要がある。この検証プロセスの概略を以下に示す。いずれかのステップでも失敗すると検証は途中で中止され、invalidが返される。

  1. PoPのフォーマットをチェックする。入力が使用済みであることを除いて、通常のトランザクションの検証をパスする必要がある。
  2. ロックタイムが499999999かチェックする。
  3. 出力は1つだけかチェックする。この出力のコインの量は必ず0で、上記のOP_RETURN出力フォーマットに準拠している必要がある。
  4. nonceがリクエストで送信したものと同じかチェックする。
  5. シーケンス番号が0であること以外は、PoPの入力が全てトランザクションTと全く同じであることを確認する(署名は除外)。この時入力の順序もTと同じこと。
  6. 全入力のスクリプトを実行し、全てtrueを返すこと。
  7. PoP出力に書かれているtxidが、実際に証明対象のトランザクションであるかチェックする。もし、どのトランザクションか知らない場合は、実際の製品やサービスに支払われたトランザクションを確認すること。
  8. 上記全てパスしたらvalidを返す。

セキュリティの考慮事項

  • 誰かがPoPリクエストをインターセプトし、その中のパラメータを変更する可能性があるが、これはSSLなどの安全な接続を使うことで軽減できる。以下のような改竄が考えられる↓
    • PoPの送付先を変更し、PoPを盗む
    • labelを変更し、意図しないPoPに署名したり、ウォレットに記録がないラベルが設定されるとサービスが利用できなくなる。
    • nonceを変更することで、PoPはサーバー上の検証で失敗する。
  • PoPリクエストを改竄してPoPを盗み、nonceが一致するまでサーバにリクエストを送る。実際に合致する確率は1 / 248。サーバーにはこの種の総当り攻撃を検出する仕組みを用意するか、少なくともPoPリクエストを100ms遅らせるなどしてプロセスを遅くする。
  • ウォレットに資金がない場合でも、PoP生成器としての価値がある可能性がある。そのためウォレットの残高が0でもセキュリティを維持することは重要。
  • トランザクションのmalleabilityによりサーバーとクライアントで異なるtxidを認識している可能性がある。その場合、クライアントはサーバーに対しトランザクションの証明ができない。ウォレットは自身がブロードキャストした時のtxidに依存してはいけない。代わりにネットワーク上のトランザクションをチェックし、そのトランザクションをリストに加えるなければならない。

参照実装

所感

  • 特殊なスクリプトなんかを設計すると、スクリプトタイプに依存して署名方法が複雑になったりするので、↑みたいに無効なトランザクション作って署名するパータンはありだなー。
  • 入力順も検証ルールに入ってるのは何でかな?単純にシンプルにしたいから?
  • 今のプロトコル仕様のままでも結構利用シーンありそうだけど、そんなに話題に上がらないのは別にもっと良いプロトコルある?