Y

Reality Keysと連携するiOSアプリをつくる 第5回 claim(出金)

f:id:yzono:20150224145637j:plain

はじめに

今回は一連の最後の処理「claim(出金)」をやります。

目次

  • By My Coin Web版はどうしているか
  • iOS版はどう実装するか
  • unspentトランザクション取得
  • Winnerのprivateキー取得
  • iOSでの実装
  • 結果確認

By My Coin Web版はどうしているか

以前この記事で調べました。

iOS版はどう実装するか

まず必要なデータを整理します。

to_addr = destination
p2shaddress = P2SHAddress
yes_user_pubkey = yes pubkey(Mnemonicから作成したPubkey)
no_user_pubkey = no pubkey(チャリティ先)
yes_pubkey = RK APIが返すyespubkey
no_pubkey = RK APIが返すnopubkey
txes = unspentトランザクション
winner_privkey = RK APIが返すwinnerのprivate key

claim時に取得が必要なのは、unspentトランザクションとwinner_privkeyの2つです。 winner_privkeyは、RK結果取得APIにより取得。txesはblockr.ioから取得します。

unspentトランザクション取得

unspentトランザクション取得はblockr.ioから以下のように取得できます。

https://btc.blockr.io/api/v1/address/unspent/35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT

{
  status: "success",
  data: {
    address: "35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT",
    unspent: [
      {
        tx: "89cda9cdf7a78005cb33f12383eb69b90957cbc4fde1ad2833bddb614f551ead",
        amount: "0.00050000",
        n: 0,
        confirmations: 1,
        script: "a9142a30fa7829a2c326387252d53087eb822269c2bb87"
      },
      {
        tx: "9214ea8b867a772e868f8aea98cd0da8a32811d703af84492abe61e253a50231",
        amount: "0.00050000",
        n: 0,
        confirmations: 1,
        script: "a9142a30fa7829a2c326387252d53087eb822269c2bb87"
      }
    ]
  },
  code: 200,
  message: ""
}

Winnerのprivateキー取得

WinnerのprivateキーはRKのAPIから取得します。

https://www.realitykeys.com/api/v1/runkeeper/1927/?accept_terms_of_service=current

{
  no_pubkey: "02c0b9fba37cfd406874d34a8b1ce133f9ff5b1915f9e27ec07dff12fefc7611ec",
  user_profile: "1869270714",
  settlement_date: "2015-02-15",
  objection_period_secs: 604800,
  human_resolution_scheduled_datetime: null,
  measurement: "total_distance",
  evaluation_method: "ge",
  is_user_authenticated: true,
  objection_fee_satoshis_paid: 0,
  machine_resolution_scheduled_datetime: "2015-02-15 00:00:00",
  user_id: "40442259",
  goal: "1000",
  created_datetime: "2015-02-14 15:56:50",
  winner: "No",
  value: "1000",
  id: 1927,
  source: "runkeeper",
  yes_pubkey: "02a5d4a751923c4128332df96887e188c86418820c437b0891fa838cc4ad104a58",
  activity: "running",
  objection_fee_satoshis_due: 1000000,
  user_name: "zono",
  winner_privkey: "L3jyBVqNFkwzTRoc4c3XsDtSZAVr5jcZRA21Ewh2pLQss3vCgk4f"
}

上記の結果からNOがWinnerであることがわかります。

iOSでの実装

JavaScript

By My Coins Web版ではブロードキャスト時にeligiusを使っていますが、iOS版ではBlockchain.infoを使います。

function execute_claim(to_addr, p2sh_address, yes_user_pubkey, no_user_pubkey, yes_pubkey, no_pubkey, txes, winner_privkey) {
    
    // 今回NOが勝ったのでダミーでNOの秘密鍵を設定
    user_privkey = "***************************************";
    
    var c = {
        'address': p2sh_address,
        'yes_user_pubkey': yes_user_pubkey,
        'no_user_pubkey': no_user_pubkey,
        'yes_pubkey': yes_pubkey,
        'no_pubkey': no_pubkey
    }
    
    var unspent_txes = JSON.parse(txes);
    for (var i = 0; i < unspent_txes.data.unspent.length; i++) {
        console.log(unspent_txes.data.unspent[i].tx);
        
        var txHex = hex_for_claim_execution(to_addr, user_privkey, winner_privkey, unspent_txes.data.unspent[i], c);
        blockchain_cross_domain_post(txHex);
    }
}

function hex_for_claim_execution(to_addr, priv1, priv2, tx, c) {
    var tx = tx_for_claim_execution(to_addr, priv1, priv2, tx, c);
    var txHex =  tx.serialize().toString('hex');
    return txHex;
}

function tx_for_claim_execution(to_addr, priv1, priv2, tx, c) {
    
    var network = bitcore.networks['livenet'];
    
    var n = tx['n'];
    var txid = tx['tx'];
    var amount = tx['amount'];
    
    var utxos2 = [
                  {
                  address: c['address'],
                  txid: tx['tx'],
                  vout: tx['n'],
                  ts: 1396375187,
                  scriptPubKey: tx['script'],
                  amount: amount,
                  confirmations: 1
                  }
                  ];
    
    var pubkeys = [
                   [ c['yes_user_pubkey'], c['yes_pubkey'] ],
                   [ c['no_user_pubkey'], c['no_pubkey'] ],
                   [ c['yes_user_pubkey'], c['no_user_pubkey'] ]
                   ];
    
    var opts = {network: network, nreq:[2,2,2], pubkeys:pubkeys};
    var fee = 10000 / 100000000;
    
    outs = [{address:to_addr, amount:(amount-fee)}];
    
    var hashMap = {};
    hashMap[ c['address'] ] = redeem_script(c);
    
    var b = new bitcore.TransactionBuilder(opts);
    b.setUnspent(utxos2);
    b.setHashToScriptMap(hashMap);
    b.setOutputs(outs);
    
    var user_wk = new bitcore.WalletKey({ network: network });
    user_wk.fromObj( {
                    priv: priv1,
                    });
    var user_wk_obj = user_wk.storeObj();
    var user_privkey_wif = user_wk_obj.priv;
    
    var winner_wk = new bitcore.WalletKey({ network: network });
    winner_wk.fromObj( {
                      priv: priv2,
                      });
    var winner_wk_obj = winner_wk.storeObj();
    var winner_privkey_wif = winner_wk_obj.priv;
    
    b.sign([user_privkey_wif, winner_privkey_wif]);

    tx = b.build();
    return tx;
}

function blockchain_cross_domain_post(data) {
    var form = document.createElement("form");
    form.setAttribute("method", "POST");
    form.setAttribute("action", 'https://blockchain.info/ja/pushtx');
    
    var hiddenField = document.createElement("input");
    hiddenField.setAttribute("type", "hidden");
    hiddenField.setAttribute("name", "tx");
    hiddenField.setAttribute("value", data);
    
    form.appendChild(hiddenField);
    
    document.body.appendChild(form);
    form.submit();
}

Swift

blockr.ioからunspentトランザクションを取得してJSON文字列をJavaScriptに渡しています。(JSONのパースはJS側で行う)

var json: String!

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    Alamofire.request(.GET, "https://btc.blockr.io/api/v1/address/unspent/35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT", parameters: nil)
        .responseJSON { (request, response, data, error) in
            let jsonObject = JSON(data!)
            self.json = jsonObject.rawString(encoding: NSUTF8StringEncoding, options: nil)
            
            dispatch_async(dispatch_get_main_queue(),{
                let path = NSBundle.mainBundle().pathForResource("index", ofType: "html")!
                let url = NSURL(string: path)!
                let urlRequest = NSURLRequest(URL: url)
                self.webView.loadRequest(urlRequest)
            });
    }
}

func webViewDidFinishLoad(webView: UIWebView) {
    
    let userDefaults = NSUserDefaults.standardUserDefaults()
    let mnemonic = userDefaults.stringForKey("mnemonic")
    let k = webView.stringByEvaluatingJavaScriptFromString("pubkey_for_mnemonic('\(mnemonic!)');")
    
    let to_addr = Constants.PublicKey.CharityAddress
    let p2shaddress = "35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT"
    let yes_user_pubkey = k!
    let no_user_pubkey = Constants.PublicKey.CharityPubKey
    let yes_pubkey = contract.yes_pubkey
    let no_pubkey = contract.no_pubkey
    let txes = json
    let winner_privkey = contract.winner_privkey
    
    webView.stringByEvaluatingJavaScriptFromString("execute_claim('\(to_addr)','\(p2shaddress)','\(yes_user_pubkey)','\(no_user_pubkey)','\(yes_pubkey)','\(no_pubkey)','\(txes)','\(winner_privkey)');")
}

結果確認

https://blockchain.info/address/35Y6ygMF6ZZwchVTzGeNTg7QBsRFf1G8rT

2つ問題があります。

  1. 2つのunspentトランザクションがあるのに1つした出金されていない。
  2. 30分近くたってもUnconfirmation状態。

1はプログラムのバグ。2はbitcoind 0.1.0の普及でそのうち解決するかもしれません。

まとめ

課題はたくさんありますが、とりあえず一連の流れを実装できました。次回からは使いやすいUIを考えてみます。

参考

JavaScript post request like a form submit