Develop with pleasure!

福岡でCloudとかBlockchainとか。

SIGHASH_FORKIDとfork idを使ったリプレイプロテクションの仕組み

8/1にBitcoin Cashが分岐した際、BTCとの間のリプレイアタックに対する保護の仕組みを組み込む必要があり、この時SIGHASHを利用する仕組みが導入された。

spec/replay-protected-sighash.md at master · Bitcoin-UAHF/spec · GitHub

最近分岐したBitcoin Gold(BTG)も同じ仕組みを採用したようだ。

SIGHASH_FORKID

通常トランザクションに署名する際には、トランザクションのどのスコープまで署名するか決めるのにSIGHASH TYPEと呼ばれるものをセットするようになっている↓

techmedia-think.hatenablog.com

基本となるSIGHASH_ALLSIGHASH_NONESIGHASH_SINGLEの3つタイプにSIGHASH_ANYONECANPAYというフラグを組み合わせることで計6通りの署名スコープをセットできるようになっている。
(ほとんどのトランザクションSIGHASH_ALLを使っている。)

フォークを識別するため、SIGHASH_ANYONECANPAYのように追加されたフラグがSIGHASH_FORKIDで以下の値がフラグとして決められている。

SIGHASH_FORKID = 0x40

HF後、このフラグがセットされていないトランザクションはBCHやBTGのブロックチェーン上では無効なトランザクションと判断される。尚、BTCのチェーンでは、セットされているSIGHASH_TYPEからSIGHASH_ANYONECANPAYを除去した結果、その値がSIGHASH_ALL(0x01)SIGHASH_SINGLE(0x03)の間でなければ署名のエンコードチェックでエラーになる。つまり、 SIGHASH_FORKID(0x40)フラグがセットされていればエラーになる。

署名スコープがSIGHASH_ALLでBCHやBTGのトランザクションの署名を作る際、そのSIGHASH TYPEは

SIGHASH_ALL(0x01) | SIGHASH_FORKID0x40) = 0x41

になる。

実際にBitcoin Cashトランザクション

https://www.blocktrail.com/BCC/tx/50c22f9cb1fbeec34fb9a77fdd54bcfa65c6ae65deebc6e06655867d0d659b3d

のインプットのscriptSigを見てみると↓

304502210085b726543e566fe2921f801f548e18bfb67662de920f0264bee6086e610af553022011103703f8cbc19c33c213e546da169434f3334c20f456fdf36fd2ea33fe4e1b41 
033489bdf777f135fa9ab0f31bd795a60a56d7daddc0ae18c6c1ad4d8589d2c72e

1つめの要素が署名で2つめの要素が公開鍵。署名の最後にはSIGHASH TYPEをセットする決まりなので、上記から41 = SIGHASH_ALL | SIGHASH_FORKIDになっていることが分かる。

Bitcoin Goldのトランザクションも↓

https://btgexp.com/api/getrawtransaction?txid=6673dd19df842c2777201718d95edca600f72de6bf508727604ddb0834212323&decrypt=1

41 = [ALL|FORKID]が適用されていることが分かる。

fork id

BCHもBTGもどちらもSIGHASH_FORKIDフラグが付与されているのは分かったが、これだけだとBTC⇔BCH,BTGのリプレイ保護にはなるけどBCH⇔BTG間のリプレイ保護にはならない。そのため共通のSIGHASH_FORKIDフラグとは別に各チェーンを識別するためのfork idが存在する。各チェーンのfork idの値は以下の通り。

チェーン fork id
BCH 0
BTG 79(金の原子番号

このfork idは、署名対象のトランザクションのダイジェストデータであるSIGHASHを生成する際に加味される。BCHもBTGもトランザクションのダイジェストデータを生成する仕様は、BTCのsegwitで導入されたBIP-143のルールに従う↓(BCHはsegwit導入はしていないけどこの仕様だけは導入している)

techmedia-think.hatenablog.com

オリジナルのBIP-143と唯一違うのは、このSIGHASHを生成する際に使用するSIGHASH TYPEの最上位bitにfork idを含める点だ。BCHのfork idは0なので実質何もする必要はないが、BTGの場合は79なので↓のようにSIGHASH TYPEにfork idのビット和を適用する必要がある。

hash_type = hash_type | (79 << 8)

これにより、BTGのトランザクションをBCHのチェーンで署名検証すると、署名対象データであるSIGHASHを生成する際に使用する値(fork id)が違っているので署名検証に失敗し、リプレイ攻撃から保護できるという仕組みだ。

以上のSIGHASH_FORKIDfork idがリプレイプロテクションの仕組みなので、この仕組みを採用しているBTC派生チェーンで有効なトランザクションを作成する際は、

  1. トランザクション署名時のSIGHASH TYPEにSIGHASH_FORKIDフラグを適用
  2. SIGHASH生成時に使用するSIGHASH TYPEの最上位bitにfork_idを適用(BCHのみ省略可)

すれば、それぞれのチェーンで有効なトランザクションが生成できる。トランザクションについては他はBTCと変わらないみたいなので、各チェーンのコインを送金したい場合は↑のルールに則って署名すればいいだけ。

気になる点

  • この仕組みはSIGHASHを使った仕組みなので、当然ながらOP_CHECKSIGOP_CHECKMULTISIGを使用せず署名検証が必要のないスクリプトの場合、リプレイ保護は提供されない(まぁほとんどは署名検証をするスクリプトになっているということだろう)。
  • SIGHASH_FORKIDは署名の最後のSIGHASH TYPEを確認すれば簡単に分かるが、fork idはSIGHASH生成時の1要素なだけなので署名データから明示的に確認する方法はなく、そのチェーンで有効なfork idなのかは署名検証するまで分からない。
  • 署名検証のコストも考慮すると、ネットワークのservice bitsなんかで互いを識別して接続しないようにするとかした方が、各ノードが無駄なコストを負担しなくて済むように思うけどどうなんだろう?

Scriptless Scriptで実現するAtomic Swap

Atomic Swapといえば同じハッシュ関数とタイムロックの仕組みを持つブロックチェーンであれば、それぞれのチェーンの通貨をトラストレスに交換することができるプロトコルで、最近だとBitcoinやLitecoin、BitcoinとEthereumでAtomic Swapの事例が出てきている。

ハッシュのプリイメージとタイムロックの仕組みを使ったこのプロトコルの詳細は↓

techmedia-think.hatenablog.com

このプロトコルでは、各チェーンで以下のようなスクリプトにコインをロックしている。

IF
  2 <アリスの公開鍵> <ボブの公開鍵> 2 CHECKMULTISIGVERIFY
ELSE
  HASH160 <H(x)> EQUAL <ボブの公開鍵> CHECKSIGVERIFY
ENDIF

↑はBitconのスクリプト言語だが、Ethereumでは同様の機能を持つContractを実装することになる。

ハッシュのプリイメージとタイムロックを使うこのプロトコルには、2つのチェーンにおいて同じプリイメージ及びそのハッシュを使用しているのでチェーン間のトランザクションがリンクできるというプライバシーの課題がある。MASTが利用可能になれば取引の当事者が協調することでこの課題を解消する方法もあるが、こういったハッシュのプリイメージを使用するスクリプトを使わずに暗号技術を使ってAtomic Swapを実現するScriptless Scriptというアプローチがある。

スクリプト無しでどうやってAtomic Swapを行うかは、Scaling Bitcoin 2017でもScriptless Scriptsについて話をしたBlockstreamのAndrew Poelstraが、以下のドキュメントに書いている。

github.com

内容についてざっと見てみる。

Schnorr署名とAdaptor Signature

楕円曲線のジェネレータをGとし、Hをビット文字列の空間から(Gの位数を法とする整数である)スカラーグループにマッピングするハッシュ関数としたとき、公開鍵Pを有するメッセージmのSchnorr署名は以下の式を満たす(s, R)のペアになる。

sG = R + H(P || R || m)P

これに関連するAdaptor Signatureは以下の式を満たす(s', R, T)として定義される。

s'G = T + R + H(P || R || m)P

Schnorr署名(s, R)とAdaptor Signature(s', R, T)から(それぞれRが同じであることに注意)、Tの離散対数をs' - sで計算することができる。上記の式を減算すると(s' - s)G = Tになるため。

同様に、Adaptor Signature(s', R, T)T = tGとなるtが与えられると、s = s' - tでSchnorr署名(s, R)を計算することができる。

公開鍵Pを有するAdaptor Signature(s', R, T)が与えられると、同じP及びRを有するSchnorr署名の知識はTの離散対数の知識と等しいと結論付けることができる。

このAdaptor SignatureがAtomic Swapのハッシュのプリイメージの代わりに使われる。

Schnorr署名のマルチシグ

Schnorr署名には複数の署名を集約することができる特性がある。公開鍵PQを持つ2人の当事者が対話的にPQのマルチシグを作成することが可能だ。署名のコンポーネント(s, R)は、両当事者の提出した署名鍵の合計になる。まず最初に両当事者は署名で使用するRの値について同意し、続くステップで各当事者がそれぞれの署名鍵を提供しsを作る。

スクリプトレスなAtomic Swapのプロトコル

スクリプトレスなAtomic Swapは↑のSchnorr署名のマルチシグとAdopter Signatureを組み合わせたプロトコルになる。

アリスはAチェーンに、ボブはBチェーンにコインをそれぞれ持っており、それぞれのチェーンのコインをアトミックに交換したい場合、以下の手順でAtomic Swapを行う。

f:id:techmedia-think:20171111133601p:plain

  1. まずアリスとボブはそれぞれコインを持っているチェーンで、使用するには両者の署名が必要なマルチシグのアウトプットにコインを入れる。またこの時、最終的にコインを償還する際の署名に使用するRの値についてお互いに合意する。
  2. 続いてアリスはランダムな値tを生成し、T = tGを算出する。続いてマルチシグの署名値のsを作成するためのアリスの署名鍵を渡す代わりにAdaptor Signature(s', R, T)を生成して、ボブに送る。(T + R + H(P || R || m)PPの部分はマルチシグなので、おそらくアリスの公開鍵とボブの公開鍵を結合したものになる)
  3. ボブは受け取ったAdaptor Signatureを検証し問題なければ(各チェーンのAdaptor Signatureで同じTが使われている=離散対数が同じことをを確認する)、マルチシグの署名データsに必要な自分の署名鍵を明らかにする。
  4. アリスはボブから受け取った署名鍵と自分の署名鍵を組み合わせてsを導出し、マルチシグの署名(s, R)を完成させる。それをパブリッシュしてコインを入手する。
  5. ボブは手順2でアリスから受け取ったAdaptor Signatureと手順4でアリスが公開した署名からシークレットtを計算する(Schnorr署名とAdaptor SignatureからTの離散対数tを計算できる)。算出したtを使ってアリスの署名鍵を計算する。
  6. ボブは計算したアリスの署名鍵に自分の署名鍵を加えて署名を完成させ、その署名でコインを入手する。

※ 手順2でアリスからAdaptor Signatureが送られてこなかったり、2つのAdaptor SignatureのTが違っていたり、手順4でアリスがトランザクションをブロードキャストしない場合、予めlocktimeを設けていた払い戻し用のトランザクションをブロードキャストして自分のコインを取り戻す。

という手順でSchnorr署名のマルチシグとAdaptor Signatureを利用することで、スクリプトを書くことなくクロスチェーンのAtomic Swapを行うことができる。

この時ブロックチェーンに記載されるのはSchnorr署名のデータのみであるため、アリスとボブの間でAtomic Swapが行われたことを他のユーザーが検知することはできず、プライバシーに優れている。

↑のプロトコルはAtomic Swapを行う各チェーンで同じ楕円曲線を使用する前提になっているが、Scaling Bitcoin 2017でのAndrew Poelstraのセッションでは今後異なる楕円曲線間(Bitcoinのsecp256k1とMoneroのed25519など)のスワップのサポートを追加していくとのこと。

Andrew Poelstraの記事を読むことで、Scriptless Scriptの仕組みがふんわりと分かった気がする。Schnorr署名は単純に署名を集約してサイズ削減するだけでなく、こういった暗号学的なトリックに使うことができるのは面白い。バックボーンの原理についてはよく分かってないので勉強しないとなー。

OpenSCでマイナンバーの登録情報を確認してみた

OpenSCにマイナンバーカード(JPKI)の対応がマージされていたので、OpenSCを使ってマイナンバーカードの登録情報を確認してみる。

github.com

使用したカードリーダーは↓でMacでも認識できた。

amzn.to

カードに登録されているオブジェクトのリスト確認

まずカードに登録されているオブジェクトを確認してみる。

$ pkcs15-tool --dump
Using reader with a card: Gemalto PC Twin Reader
PKCS#15 Card [JPKI]:
    Version        : 0
    Serial number  : 00000000
    Manufacturer ID: JPKI
    Flags          : 
PIN [User Authentication PIN]
    Object Flags   : [0x12], modifiable
    ID             : 01
    Flags          : [0x12], local, initialized
    Length         : min_len:4, max_len:4, stored_len:0
    Pad char       : 0x00
    Reference      : 1 (0x01)
    Type           : ascii-numeric
    Tries left     : 3

PIN [Digital Signature PIN]
    Object Flags   : [0x12], modifiable
    ID             : 02
    Flags          : [0x12], local, initialized
    Length         : min_len:6, max_len:16, stored_len:0
    Pad char       : 0x00
    Reference      : 2 (0x02)
    Type           : ascii-numeric
    Tries left     : 5

Private RSA Key [User Authentication Key]
    Object Flags   : [0x1], private
    Usage          : [0x4], sign
    Access Flags   : [0x1D], sensitive, alwaysSensitive, neverExtract, local
    ModLength      : 2048
    Key ref        : 1 (0x1)
    Native         : yes
    Auth ID        : 01
    ID             : 01
    MD:guid        : c5a0a252-9d2d-eb60-fec0-41b4fbd722a2

Private RSA Key [Digital Signature Key]
    Object Flags   : [0x1], private
    Usage          : [0x204], sign, nonRepudiation
    Access Flags   : [0x1D], sensitive, alwaysSensitive, neverExtract, local
    ModLength      : 2048
    Key ref        : 2 (0x2)
    Native         : yes
    Auth ID        : 02
    ID             : 02
    MD:guid        : e1bc1dae-59f1-16ab-b43f-9dafbb2acc9b

Public RSA Key [User Authentication Public Key]
    Object Flags   : [0x0]
    Usage          : [0x0]
    Access Flags   : [0x2], extract
    Key ref        : 1 (0x1)
    Native         : yes
    Path           : 000a
    ID             : 01

Public RSA Key [Digital Signature Public Key]
    Object Flags   : [0x0]
    Usage          : [0x0]
    Access Flags   : [0x2], extract
    Key ref        : 2 (0x2)
    Native         : yes
    Path           : 0001
    ID             : 02

X.509 Certificate [User Authentication Certificate]
    Object Flags   : [0x0]
    Authority      : no
    Path           : 000a
    ID             : 01
    Encoded serial : 02 03 1C3301

X.509 Certificate [Digital Signature Certificate]
    Object Flags   : [0x1], private
    Authority      : no
    Path           : 0001
    ID             : 02

X.509 Certificate [User Authentication Certificate CA]
    Object Flags   : [0x0]
    Authority      : yes
    Path           : 000b
    ID             : 03
    Encoded serial : 02 01 01

X.509 Certificate [Digital Signature Certificate CA]
    Object Flags   : [0x0]
    Authority      : yes
    Path           : 0002
    ID             : 04
    Encoded serial : 02 01 01
  • JPKIのカードオブジェクト
  • 認証用のPIN
  • 署名用のPIN
  • 認証用のRSA秘密鍵
  • 署名用のRSA秘密鍵
  • 認証用のRSA公開鍵
  • 署名用のRSA公開鍵
  • 認証用のX.509証明書
  • 署名用のX.509証明書
  • 認証用の認証局のX.509証明書
  • 署名用の認証局のX.509証明書

が登録されていることが分かる。

証明書の確認

↑のうち、ユーザーの証明書は認証用のX.509証明書と署名用のX.509証明書。

まずユーザー認証用のX.509証明書の内容を確認してみる。

$ pkcs15-tool --read-certificate 1 > user-auth.pem
Using reader with a card: Gemalto PC Twin Reader
$ openssl x509 -text -noout -in user-auth.pem

opensslコマンドで証明書の内容を確認できる。

X509v3 Issuer Alternative Name: 
   DirName:/C=JP/O=\xE5\x85\xAC\xE7\x9A\x84\xE5\x80\x8B\xE4\xBA\xBA\xE8\xAA\x8D\xE8\xA8\xBC\xE3\x82\xB5\xE3\x83\xBC\xE3\x83\x93\xE3\x82\xB9
X509v3 CRL Distribution Points: 
   DirName:/C=JP/O=JPKI/OU=JPKI for user authentication/OU=CRL Distribution Points/OU=Fukuoka-ken/CN=Iizuka-shi CRLDP

発行者は"公的個人認証サービス"で、CRLのDistribution Pointsは福岡県飯塚市になってる。各市町村が証明書の失効リストを管理していることになる?

この認証用の証明書には個人情報は掲載されていない。

個人情報が含まれているのは、署名用のX.509証明書で、個人の氏名、生年月日、性別、住所といった本人確認に必要な情報が含まれている。続いてこの証明書の内容を確認してみる。

先程と同様にpkcs15-toolで確認しようとすると

$ pkcs15-tool --read-certificate 2 > user-signature.pem
Certificate read failed: Security status not satisfied

とエラーになる。どうやらこっちはPINでロックされてるみたい。

なので--verify-pinオプションでPINの検証をする。この時、署名用のPINのIDは02なので--auth-idオプションでそのPINを指定して実行する。すると署名用のPINの入力を求められるので入力すると署名用のX.509証明書が表示される。

$ pkcs15-tool --read-certificate 2 --verify-pin --auth-id 02
Using reader with a card: Gemalto PC Twin Reader
Please enter PIN [Digital Signature PIN]: 
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

証明書のX509v3 Subject Alternative Nameに個人情報が記載されているが、UTF-8で記載されているこのデータはopensslコマンドで確認しても

othername:<unsupported>, othername:<unsupported>, othername:<unsupported>, othername:<unsupported>, othername:<unsupported>, othername:<unsupported>

となるだけなので、別途OpenSSL::ASN1などでパースする必要がある。

rubyでパースするサンプル↓

require 'openssl'

cert = OpenSSL::X509::Certificate.new File.read('証明書のパス')
subject_alt_name = cert.extensions.find {|e| e.oid == 'subjectAltName'}

asn_san = OpenSSL::ASN1.decode(subject_alt_name)
asn_san_sequence = OpenSSL::ASN1.decode(asn_san.value[1].value)

asn_san_sequence.each do |asn_data|
  key = asn_data.value[0].value
  value = asn_data.value[1].value[0].value
  puts "#{key}: #{value}"
end

証明書に記載されている個人情報が確認できる。

最後に

公的認証された証明書やキーペアを日本国民であれば誰でも手に入れられ、それらを利用すれば各サービス毎の煩わしい本人確認のプロセスは簡略化できると思うので、今後そういったサービスが充実していくのに期待したい。まぁカードの場合リーダーとセットで不便なので、スマホなどへのインポートができるとさらに便利になると思う。

まぁ、本当に秘密鍵を知ってるは本人だけなのか?という疑念はあるけどなー。マイナンバー登録時に鍵にアクセスする際のPINの登録はするけど、その際に秘密鍵作ってるわけではないし。

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で鍵導出してる。