2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

BIP47 Payment Code を bitcoinrb (Ruby) で実装してみた

Last updated at Posted at 2019-12-25

Payment Code

Payment Code は BIP47 で定義されるている Bitcoin のアドレスの形態の一つです。
bips/bip-0047.mediawiki at master · bitcoin/bips
Bitcoin においてアドレスはプライバシーの観点から再利用することは推奨されていません。
しかし、アドレスを使い回さないということは、同じ人に送金するにもかかわらず毎回銀行口座が変わってしまう状況に近く不便です。
そこで BIP47 では Payment Code と呼ばれるアドレスの別形態を定義することで、同じ Payment Code への支払いをアドレスの再利用を行わずに実現しました。
つまり、UI上同じ Payment Code 宛に複数回の支払いを行っても、Bitcoin プロトコル上では毎回別のアドレスへの送金が行われているのです。
また受け取り側も、複数回の支払いを受けた場合でも送金元ユーザー(Payment Code)を知ることができます。(たぶんそういうことだと思っている。ここ自信ない。)

実装例

もっともメジャーな実装は Samourai Wallet の Paynym だと思います。
Java 実装です。

環境

bitcoinrb v0.3.1

Payment Code の仕組み

この記事では言葉で説明しています。
くわしい仕様は BIP を参照してください。
Gist のコードは Test Vector を満たすように動くはずです。

定義

Payment Code は 基本的には BIP32(HD Wallet) 1です。
BIP32 に関連して BIP442 では以下のようなパスの標準形が提案されています。
m / purpose' / coin_type' / account' / change / address_index
これに基づいて、Payment Code では purpose47'にした BIP32に基づく アドレス郡(なにかもっといい言葉があれば...)から Payment Code を導出します。
Payment Codeの定義は BIP47 の binary serialization に記載されています3
Bytes 3 - 34x value が extended key の x軸の値になります。

bitcoinrb で実装すると

sample.rb
bip32_master = Bitcoin::ExtKey.generate_master(seed)
ext_key = bip32_master.derive(47, harden=true).derive(0, harden=true).derive(0, harden=true)

chain_code = ext_key.chain_code

byte3_34 = ext_key.pub.slice(2...ext_key.pub.length) # x of pubkey
byte35_66 = chain_code.unpack('H*').first # chain code

raw_payment_code = '47' + '01' + '00' + '02' + byte3_34 + byte35_66 + ('0' * 26)

payment_code = Bitcoin::Base58.encode(raw_payment_code + Bitcoin.calc_checksum(raw_payment_code))

Notification Transaction

メインの送金を行う前に、送金元が送金先に、送金元の Payment Code を通知する Notification Transaction が必要です。
Notification Transaction を行うことで以下の2点が実現できます。

  • Refund
  • 支払いのコンテキスト

今回は Refund については触れられません。そこまで実装していないので...。
Payment Code に送られてくる送金は、受信したアドレスによって誰からの送金であるかを知ることができます。
サービスが定期的な支払いを受ける際などに、誰からの支払いが済んでいるかが容易に確認できます。

Notification Transaction の作り方

Alice から Bob への送金を想定します。
Alice が Bob の Notification Address へ Transaction を作成し、その OP_RETURN に以下の手順で作成するマスクされた Alice の Payment Code を入れます。これが Notification Transaction となります。

Masked Payment Code の作り方

Alice は Notification Transaction の input となるトランザクションの Alice の秘密鍵と Bob の Notification Address の公開鍵から secp256k1 上の点を導出します。
Alice はその点を元に、Alice の Payment Code をマスクするための blinding factor を計算します。
Payment Code を構成するバイト列のうち xを blinding factor の前半 32バイトで、chain code を 後半 32バイトでそれぞれ xor を取り、それらを元に Payment Code を導出します。
これがマスクされた Alice の Payment Code です。
blinding factor のもととなる点は ECDH によって Bob と共有されます。したがって Bob は Alice が使用した blinding factor を導出できるため、マスクされた Payment Code から 実際の Alice の Payment Code を得ることができます。

bitcoinrb で実装すると

bip47_ext.rb
Alice_Ext = Bitcoin::ExtKey.generate_master(alice_seed)
Alice_Ext_47 = Alice_Ext.derive(47, harden=true).derive(0, harden=true).derive(0, harden=true)

Bob_Ext = Bitcoin::ExtKey.generate_master(bob_seed)
Bob_Ext_47 = Bob_EXT.derive(47, harden=true).derive(0, harden=true).derive(0, harden=true)
sample.rb
# Alice のプライベートキーと Bob 公開鍵から secp256k1 上の点を導出
alice_key = Bitcoin::Key.from_wif('Kx983SRhAZpAhj7Aac1wUXMJ6XZeyJKqCxJJ49dxEbYCT4a1ozRD')
alice_ec = OpenSSL::PKey::EC.new('secp256k1')
alice_ec.private_key = OpenSSL::BN.new(alice_key.priv_key, 16)

bob_pub_bn = OpenSSL::BN.new(Bob_Ext_47.derive(0).pub, 16)

## secp256k1 上の点
secret_point = OpenSSL::PKey::EC::Point.new(alice_ec.group, bob_pub_bn)

## Alice と Bob が共有する Shared secret
common_key = alice_ec.dh_compute_key(alice_secret_point).bth

送金

Notification Transaction 同様、Alice の秘密鍵と Bob の公開鍵から Bob と共有できる値をつくります。
その値をもとに送金先となる公開鍵を作成します。
Bob の公開鍵を取得する際に、BIP32 におけるパスの値を増加させることで複数回の送金を実行します。

bitcoinrb で実装すると

sample.rb
# Alice to Bob Payment
## Alice selects the 0th private key derived from her payment code: 
a = Alice_Ext_47.derive(0).priv
## Alice selects the next unused public key derived from Bob's payment code, starting from zero: 
bobpub = Bob_Ext_47.ext_pubkey.derive(0).pubkey
bob_pub_bn = OpenSSL::BN.new(bobpub, 16)

## Alice calculates a secret point:
alice_ec = OpenSSL::PKey::EC.new('secp256k1')
alice_ec.private_key = OpenSSL::BN.new(a, 16)
alice_secret_point = OpenSSL::PKey::EC::Point.new(alice_ec.group, bob_pub_bn)
secret_point = alice_ec.dh_compute_key(alice_secret_point)

## Alice calculates a scalar shared secret using the x value of secret_point:
## Bob can calcurate this value.
s = Bitcoin::Util.sha256(secret_point).bth

## B + sG
sg = Key.new(priv_key: s)
sg_point = sg.to_point

### B=bob_pub_point
bob_pub_point = Bob.ext_key.derive(0).key.to_point
b_prime = bob_pub_point + sg_point

## 03 が必要だった....
b_prime = '03' + b_prime.x.to_s(16)

key = Key.new(pubkey: b_prime)
key.to_p2pkh == '141fi7TY3h936vRUKh1qfUZr8rSBuYbVBK'

Payment Code におけるコンテキスト

Bob は Alice からの Notification Transaction を受け取ったときから、b_primeへの送金をモニターします。
b_primeは Alice と Bob の間でだけ生成されるアドレスです。
Carol が Bob に BIP47 での送金を行う場合、送金先アドレスは Carol と Bob の間でだけ生成される別のアドレスになります。
これが、コンテキストです。
Bob はb_primeに送られてくる送金はすべて Alice からのものであると知ることができます。

Gist

クラスにしてほんのちょっとだけきれいにしてみたもの
https://gist.github.com/kannapoix/8207fd1c5b4d69b398bfe98ea5480bb4

今後

すべて実装する
PR出したい気持ち...
(いつになることやら)
適宜この記事は update していく予定なので関心ある方はぜひストックを!

類似する技術

プライバシー強化する技術はほかにもあるけど、実際 Payment Code ってどうなの

...。
どうなんでしょうか。
要調査。

参考

How BIP47 Reusable Payment Codes enrich Bitcoin and overall cryptocurrency user-experience
bip47 - BIP 47: Bob's first receive address? - Bitcoin Stack Exchange

  1. bips/bip-0032.mediawiki at master · bitcoin/bips

  2. bips/bip-0047.mediawiki at master · bitcoin/bips

  3. https://github.com/bitcoin/bips/blob/master/bip-0047.mediawiki#binary-serialization

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?