読者です 読者をやめる 読者になる 読者になる

Reality Keys(事実に関する認証局サービス)のrealitykeysdemo.pyを読む(1/3)

Reality Keys

f:id:yzono:20150205130848j:plain

はじめに

前回なんとなくReality Keysを動かしましたが、今回はソースコードを読んでちゃんと理解しようと思います。

Reality KeysからYESとNOの公開鍵を受け取った後の処理は、realitykeysdemo.pyで全てできます。

realitykeysdemo.pyは以下4つの機能があります。

  • 鍵の作成(execute_makekeys)
  • 契約のセットアップ(execute_setup)
  • 勝利の主張(execute_claim)
  • 払い戻し(execute_pay)

特に気になる機能の「契約のセットアップ(execute_setup)」と「勝利の主張(execute_claim)」について詳細を確認します。第1回目は「契約のセットアップ(execute_setup)」についてです。

目次

  • 前回の振り返り
  • 契約のセットアップ(execute_setup)
  • よくあるエラー
  • realitykeysdemo.pyで使用しているpybitcointoolsの機能

前回の振り返り

前回setupを実行した時の結果を以下に書きます。AliceがNOに選択した状態です。

$ python ./realitykeysdemo.py setup 1 043e90a622f851754b83c0d28bc0d4a4c619b13c70c174658a0b0dd7480a32631629734a00ba16434e6f64060ec6e3837b5d148134cd6522302b5243b34ad37543 1000  040190c6e169d2aaadb45cae2964db48d48c16b42a9cf963aceda3644068a73f3d23a33a4efd87f2e73e9482bb7425a8500a2ca19124f1c2930df2aa5e726ff119 1000 --seed [Aliceのseed]

Made p2sh address: 3GMYiDszdeGTQ8K52EozB9vLGdLuiRxHtg. Creating a transaction to fund it.
Created a transaction:
0100000002274ef58f6b3c8bf673c864cf3f19c28b57d9d53c8705d038a7a08d03557a77f30000000000fffffffff53d81574d526a76973d25a7b71796cb4dbc8217b7bccf6e56c6c32bea1e012c000000008b48304502207eae6cd4ce85b3a54e9b3877138161a1d20e999ce16eda070c584a67ec45c919022100951e08fa3dea490ef643c4a045b068838a27de2db368164ab425cb097b2f55ff0141040190c6e169d2aaadb45cae2964db48d48c16b42a9cf963aceda3644068a73f3d23a33a4efd87f2e73e9482bb7425a8500a2ca19124f1c2930df2aa5e726ff119ffffffff01d00700000000000017a914a0db448764089afb9273611efc87ee352ecda8de8700000000
Next step: The other party runs:
./realitykeysdemo.py setup 1 043e90a622f851754b83c0d28bc0d4a4c619b13c70c174658a0b0dd7480a32631629734a00ba16434e6f64060ec6e3837b5d148134cd6522302b5243b34ad37543 1000 040190c6e169d2aaadb45cae2964db48d48c16b42a9cf963aceda3644068a73f3d23a33a4efd87f2e73e9482bb7425a8500a2ca19124f1c2930df2aa5e726ff119 1000 0100000002274ef58f6b3c8bf673c864cf3f19c28b57d9d53c8705d038a7a08d03557a77f30000000000fffffffff53d81574d526a76973d25a7b71796cb4dbc8217b7bccf6e56c6c32bea1e012c000000008b48304502207eae6cd4ce85b3a54e9b3877138161a1d20e999ce16eda070c584a67ec45c919022100951e08fa3dea490ef643c4a045b068838a27de2db368164ab425cb097b2f55ff0141040190c6e169d2aaadb45cae2964db48d48c16b42a9cf963aceda3644068a73f3d23a33a4efd87f2e73e9482bb7425a8500a2ca19124f1c2930df2aa5e726ff119ffffffff01d00700000000000017a914a0db448764089afb9273611efc87ee352ecda8de8700000000

setup時に必要なパラメータは以下です。

reality_key_id = 1 their_public_key = 043e90a622f851754b83c0d28bc0d4a4c619b13c70c174658a0b0dd7480a32631629734a00ba16434e6f64060ec6e3837b5d148134cd6522302b5243b34ad37543(Bobのpublic key) their_stake_in_satoshis = 1000 your_public_key = 040190c6e169d2aaadb45cae2964db48d48c16b42a9cf963aceda3644068a73f3d23a33a4efd87f2e73e9482bb7425a8500a2ca19124f1c2930df2aa5e726ff119(Aliceのpublic key) your_stake_in_satoshis = 1000

./realitykeysdemo.py setup <reality_key_id> <their_public_key> <their_stake_in_satoshis> <your_public_key> <your_stake_in_satoshis>

実行した結果、P2SHアドレスが作成されました。ただしBobの署名はまだない状態です。

3GMYiDszdeGTQ8K52EozB9vLGdLuiRxHtg

以下はブロードキャストした後の残高です。ロックがされていてunspentには表示されなくなります。

$ pybtctool unspent 1D8AbYyryKH27yiJhUVpztthXwxQxCjMop
[]
$ pybtctool unspent 1ERnz7DafLYP4GX3brhPGMq4mq1Mmyc3Pu
[]

契約のセットアップ(execute_setup)

execute_setupの処理を順に見ていきます。

(1)ビットコインアドレス作成

PublicKeyからビットコインアドレスを作成します。

yes_winner_address = pubtoaddr(yes_winner_public_key, magic_byte(settings))
no_winner_address = pubtoaddr(no_winner_public_key, magic_byte(settings))

(2)Private Key作成

Private Keyを作成します。seedオプションが無い場合は自動で生成してくれますが、seedオプションを渡した方が分かりやすいと思います。

private_key = user_private_key(False, seed)

(3)Public Key取得

Private KeyからPublic Key取得。引数の公開鍵と比較して、自分がどちらに投票したかを判断します。

public_key = privtopub(private_key)
    if public_key == yes_winner_public_key:
        am_i_yes_or_no = 'yes'
    elif public_key == no_winner_public_key:
        am_i_yes_or_no = 'no'

(4)stake金額チェック

どちらにもstakeされていない場合はエラーを返します。

contract_total_amount = yes_stake_amount + no_stake_amount
    if (contract_total_amount == 0):
        raise Exception("Neither of the public keys supplied matched the private key supplied.")

(5)不足分チェック

stakeした分の残高が無い場合はエラーを返します。トランザクションFeeも含めて計算します。

if yes_stake_amount > 0:
        signatures_needed = signatures_needed + 1
        yes_input = spendable_input(yes_winner_address, yes_stake_amount, MIN_TRANSACTION_FEE/2, MAX_TRANSACTION_FEE/2, settings.get('inputs', None))
        if yes_input is not None:
            inputs = inputs + [yes_input]

(6)イベントのPublic Key取得

Reality Keys APIを使用してイベントのPublic Keyを取得

req = urllib2.Request(REALITY_KEYS_API % (reality_key_id))
    response = urllib2.urlopen(req)
    fact_json = simplejson.load(response)
    yes_reality_key = fact_json['yes_pubkey']   
    no_reality_key = fact_json['no_pubkey']

(7)マルチシグのredeemスクリプト作成

mk_multisig_script_if_elseメソッドで作成しています。興味あるので詳細は別途やります。

multisig_script = mk_multisig_script_if_else([[yes_winner_public_key, yes_reality_key], [no_winner_public_key, no_reality_key]])

(8)P2SHアドレス作成

redeemスクリプトからP2SHアドレスを作成します。p2sh_scriptaddrメソッドはpybitcointoolsの機能です。

pay_to_addr = p2sh_scriptaddr(multisig_script)
    if verbose:
        out.append("Made p2sh address: %s. Creating a transaction to fund it." % (pay_to_addr))

(9)トランザクション作成

トランザクションを作成します。

outputs = [{'value': contract_total_amount, 'address': pay_to_addr}]
    tx = mktx(inputs, outputs)

(10)渡されたトランザクションのチェック

引数でトランザクションが渡される場合(今回の例だとBobの場合)、渡されたトランザクションが正しいかチェックする。

signatures_done = 0
    if existing_tx is not None:
        their_tx = deserialize(existing_tx)
        our_tx = deserialize(tx)
        # Compare the transactions, except the inputs, which are signed and we don't care anyway.
        # Alternatively we could go through these and just remove the signatures, but it shouldn't matter.
        our_tx['ins'] = []
        their_tx['ins'] = []
        if serialize(our_tx) != serialize(their_tx):
            raise Exception("The transaction we received was not what we expected.")
        tx = existing_tx
        signatures_done = signatures_done + 1

(11)サイン

ブロードキャストはpybitcointoolsのsignを使用しています。トランザクションとそのインデックスを指定しています。(Yesがindex0、Noがindex1)

# Sign whichever of the inputs we have the private key for. 
    # Since we only allow one input per person, and we add them ourselves, we can assume yes is first and no is second.
    if (am_i_yes_or_no == 'yes') and (yes_stake_amount > 0):
        tx = sign(tx,0,private_key)
        signatures_done = signatures_done + 1

    if (am_i_yes_or_no == 'no') and (no_stake_amount > 0):
        tx = sign(tx,1,private_key)
        signatures_done = signatures_done + 1

(12)サイン済みトランザクション表示(Aliceが実行した時)

サイン済みトランザクションを表示します。Bobはこのトランザクションをexecute_setupのパラメータに利用します。

else:
        if verbose:
            out.append("Created a transaction:")
        out.append(tx)
        if verbose:
            out.append("Next step: The other party runs:")
            out.append("./realitykeysdemo.py setup %s %s %s %s %s %s" % (reality_key_id, yes_winner_public_key, str(yes_stake_amount), no_winner_public_key, str(no_stake_amount), tx))

(13)ブロードキャスト(Bobが実行した時)

Alice、Bob両方のサインが揃った場合(signatures_needed == signatures_done)は、作成したトランザクションをブロードキャストします。

if signatures_needed == signatures_done:
        if settings.get('no_pushtx', False):
            if verbose:
                out.append("Created the following transaction, but won't broadcast it because you specified --no_pushtx:")
            out.append(tx)
        else:
            if verbose:
                out.append("Broadcasting transaction...:")
                out.append(tx)
                pushtx(tx)
                out.append("Next step: Wait for the result, then the winner runs:")
                out.append("./realitykeysdemo.py claim %s %s %s -f [<fee>] -d [<destination_address>]" % (reality_key_id, yes_winner_public_key, no_winner_public_key))

よくあるエラー

残高不足

AliceとBobのビットコインアドレスに残高が不足している場合、以下エラーが出ます。

残高なし

$ pybtctool unspent 1D8AbYyryKH27yiJhUVpztthXwxQxCjMop
[]
$ pybtctool unspent 1ERnz7DafLYP4GX3brhPGMq4mq1Mmyc3Pu
[]
python ./realitykeysdemo.py setup 1 043e90a622f851754b83c0d28bc0d4a4c619b13c70c174658a0b0dd7480a32631629734a00ba16434e6f64060ec6e3837b5d148134cd6522302b5243b34ad37543 10000 040190c6e169d2aaadb45cae2964db48d48c16b42a9cf963aceda3644068a73f3d23a33a4efd87f2e73e9482bb7425a8500a2ca19124f1c2930df2aa5e726ff119 10000 --seed [Aliceのseed]

The temporary addresses have not yet been fully funded.
Please ask the other party to fund the following (yes):
Yes: 10000 satoshis to the address 1D8AbYyryKH27yiJhUVpztthXwxQxCjMop
Please fund the following (no):
No: 10000 satoshis to the address 1ERnz7DafLYP4GX3brhPGMq4mq1Mmyc3Pu

残高ありの場合は以下のように出力されます。P2SHアドレスを作成するとunspentトランザクションがロックされてこの一覧には表示されなくなります。

$ pybtctool unspent 1D8AbYyryKH27yiJhUVpztthXwxQxCjMop
[{"output": "b7c142ec1e323ef1137bac5c24a37686ce5473a4615eb392800970d4bf0160aa:0", "value": 20000}]
$ pybtctool unspent 1ERnz7DafLYP4GX3brhPGMq4mq1Mmyc3Pu
[{"output": "3919dfe7bc3d741b374e2f4bb65d3e90b528ac47efaab153d3f368c4d2ba098b:0", "value": 20000}]

realitykeysdemo.pyで使用しているpybitcointoolsの機能

realitykeysdemo.pyは、pybitcointoolが提供する機能を利用しています。

例えば、ブロードキャストはpybitcointoolsのpushtxを使用して、blockchain.info/pushtxに対してリクエストを送信します。

pybitcointools
Listing of main commands

* privtopub            : (privkey) -> pubkey
* sign                 : (tx, i, privkey) -> tx with index i signed with privkey
* pushtx               : (hex or bin tx) -> tries to push to blockchain.info/pushtx
* serialize_script
* deserialize_script
* deserialize

まとめ

次回は「勝利の主張(execute_claim)」について詳細を調べてみます。

参考

pybitcointool

疑問点(今度Edに聞こう)

their_stake_in_satoshisはどういう意味か

If we hadn't taken the shortcut of forcing the parties to temporarily fund addresses with the right amounts,.... のコメントの意味

IsStandard checks 現在はTrueになるのかな