2015年12月31日木曜日

Swapbotを利用してSpells of Genesisのレアカードを販売してみる


Spells of Genesisのカードを結構集めたのですが、このリスキーかつ流動性の低い資産が、ポートフォリオの結構な比率を占めていることに気づき、若干危機感を感じてきました。

まぁBTCが上げてきてるので、結果的に関連サービスへの投資がBTCのショートポジションに見えてきてしまい、少しBTCに戻しておきたいという意味合いもあります。

CounterpartyのDEXでいくらか捌こうかとも思ったのですが、DEXでの出来高が少ないのと、直接BTCで販売出来ないという問題があり、Swapbotについて調べてみました。
(単にSoGが盛り上がってないだけという説もありますが。)

ということで、今回はSpells of Genesisのカード購入でよく利用するSwapbotで、レアカードの自動販売機を設置してみます。

SwapbotにアクセスしてSwapbot Administrationを選択します。

利用にはTokenlyアカウントが必要なので、Login or Sign Up Nowからログインページを開きます。

Registerから登録ページを開きます。
画面右上にもあります。

Name、Username、Email address、Passwordを入力します。
例にSatoshi Nakamotoが出てくるあたり、Bitcoin業界でのSatoshiのネームバリューの大きさを感じます。

Tokenlyアカウントの登録が完了すると、Swapbotがユーザー情報へのアクセスを求めてくるので許可します。

ダッシュボードが表示されるので、Create a new Swapbotを選択します。

Create a New Swapbotの画面が表示されるので、まずはLook and Feelの部分でBot Name、Bot URL Slug、Bot Descriptionを決めます。
URL SlugはURLの最後の文字列です。

Custom Background Imageなどは、今回はすっ飛ばします。

次にSwaps Selling Tokensの部分で販売方法や価格を決めます。
Swap TypeをSwaps By Rate、Swaps By Fixed Amounts、Swaps By USD Amount paid in BTC or Tokensの3つあります。

Swaps By Rateだと1.2トークン購入など半端な金額が設定出来てしまうので、今回はSwaps By Fixed Amountsを使います。

Swaps Purchasing Tokensは購入用、Advanced Swap Rulesは値引きなどの設定用なので今回は無視します。

なお、一か所で複数のトークンの販売をしようとした場合、受取りの通貨が同じものだと、おすすめしないとワーニングが出ます。

同一トークンの販売で複数通貨の受け入れか、複数トークン販売でそれぞれ受け入れ通貨が別ならOKなようです。

最後にIncome Forwardingの部分で、一定量たまると自動で送金するよう設定出来るので、一応設定しておきます。
GOX怖い。

Other SettingsはConfirmationsに関するものと、ブラックリスト、ホワイトリストに関するものなので、これも無視してSave Botで設定完了です。

Please send a monthly paymentと出てきました。
monthly paymentってなんすか?

なんとなく勝手に、売買が成立した時に手数料徴収するシステムかと思っていましたが、月額利用料を支払ってレンタルするイメージのようです。

でもいくら払えばいいのかどこにも書いてないんすけど。。

で、問い合わせてみたところ一つのbotを借りるのに約7ドル/月とのこと。

ついでに詳細なマニュアルのありかを教えてくれました。
https://docs.google.com/document/d/19jvcg5VPa9BoVo8Y2_2kbmX9QsBEOh6PgyZeeMuxe8k



まぁわざわざ教えてもらっといてアレですが、とりあえずPublic Bot Addressにアクセスするといつもの画面が表示されたので、アクティブにしないまま満足してそっと閉じましたとさ。


2015年12月13日日曜日

カスタムROMとGoogleパスワード変更に潜む孔明の罠


Chromaに乗り換えたのでメモ。
ついでにXposedも入れてみた。

Chroma 12-10-2015
http://forum.xda-developers.com/nexus-6/development/rom-chroma-01-11-2015-t3000003

hellsCore b12-M
http://hc-kernel.blogspot.jp/2015/12/n6-b12-m.html

SuperSU 2.62
http://forum.xda-developers.com/showpost.php?p=64161125

Xposed
http://repo.xposed.info/module/de.robv.android.xposed.installer


という記事を書くはずだった。


が、何をやってもwi-fiが繋がらない。
wi-fiは一旦無視してログインしようとしたところ、なぜか認証時にブロックされる。

そして不正なログイン見つけたよ的なメールが届いて、強制的にパスワードを変更させられる。

諦めてROMをカタクリに変更してみたところ、wi-fiは接続成功。
そして相変わらずgoogleの認証が通らない。

Googleに問い合わせてみたところ、「君のアカウントは生きてるよ。良かったね。」みたいな自動返信が返ってくるだけ。

途方に暮れる。

ログイン失敗時に「この端末の所有者のアカウントを利用してログインしてください」と出るので、端末側かもしれないと思い完全に初期化。

さよならすべてのデータ達。
あっ。。バックアップを母艦に移してない。

。。。


「この端末の所有者のアカウントを利用してログインしてください」
「この端末の所有者のアカウントを利用してログインしてください」
「この端末の所有者のアカウントを利用してログインしてください」

あああああああああああああああああああああああああああああああ!!!!!!!!!!!(ブリブリブリブリュリュリュリュリュリュ!!!!!!ブツチチブブブチチチチブリリイリブブブブゥゥゥゥッッッ!!!!!!! )


で、色々調べたところパスワード変更後72時間は端末にログイン出来ない仕様とのこと。

これ何?常識?常識なの?

カスROMインストール⇒ログイン⇒ブロック⇒パスワード強制変更⇒72時間ブロック
いや、なんとも巧妙な罠ですわ。

とりあえず古いバックアップから復元したところ起動は出来た。
が、やはりモバイルからはGoogle系サービスが使えない。

ということで3日間のGoogle断ち生活(モバイルのみ)が始まる。



一応まともに動いたほう(はず)のカタクリの構成もメモしとく。



俺、この3日間の喪が明けたら、カタクリ入れるんだ。。

2015年10月16日金曜日

EtereumのGUI Walletを起動してみた



EthereumのGUI WalletをWindowsで起動してみたのでメモ。


以下ページの最下部にダウンロードリンクがあります。

https://github.com/ethereum/mist/releases/tag/0.3.1-beta.1


Ethereum-Wallet-win64-0-3-1.zipをダウンロードして任意のフォルダに解凍。

Ethereum-Wallet.exeといういかにもな実行ファイルがあるので、これだ!と実行。

すると、1からブロックを同期し始めました。。


もともとコマンドラインでgethを使って同期済だったのですが、--datadirオプションでデータディレクトリの場所を変更していたせいか、自動でそっちは読んでくれませんでした。

設定ファイルなどオプションの指定方法を探したのですが見つからず。


先にコマンドラインからgethを起動した状態で再度walletを起動すると、既存のものを自動で検出してくれたものの、立ち上がった画面上では同期出来ていない状態。

色々試した結果、WALLET_FOLDER/resources/nodes/geth配下にあるgeth--datadirオプションを指定して起動し、そのうえでwalletを起動すると、同期済みのものを読みにいってくれました。


アカウントが検出されないときに何度か触ったせいでアカウントが無駄に複数作成されていましたが。。


また、その後もともとのgethを起動してみたり、再度walletに同梱されていたgethを起動してみたりしているうちに、なぜかまた1から再同期し始めました。

coinbaseはあっているので大丈夫なんだと思いますが、なんだか挙動がよく分かりません。


Betaだからなのか、自分がよくわかってないだけなのか。。。

取り合えず使えたのでよしとします。


残高ゼロですし。


2015年8月18日火曜日

Counterpartyのトランザクションを解読してみる(Send)


今回もCounterpartyの勉強のために、Counterpartyのトランザクションを読んでみます。

題材は、前回と同じSpells of Genesis関連のトランザクションの中から選択した、トークンの送金を行うSendというトランザクションタイプです。

エンコード方法も前回と同じくmultisigを使ったものを選択しました。

https://www.blockscan.com/txInfo/11635040

  • データの準備
まずはvoutの一つ目のscriptPubKeyから以下を抜き出します。

OP_1
033f2f85f4c4c200d2a6ae3c300b4a1976e86eba833cdc6b96aa653426850723d9
0296a3f26419b9e6cbc794c67c874ec3d0dca008da66427fdbc9fbd45bf24b0249
02076aee3ebdd93955b70ee616de7867aa451a990ad53e0eaad0893626f231721d
OP_3
OP_CHECKMULTISIG

前回と同じくデコードしていきます。

前半2つのダミー公開鍵を、それぞれ前後1byteずつ削って結合させます。
3f2f85f4c4c200d2a6ae3c300b4a1976e86eba833cdc6b96aa65342685072396a3f26419b9e6cbc794c67c874ec3d0dca008da66427fdbc9fbd45bf24b02

これは暗号化されているので、複合化するためにvinの最初のtxidを探してきます。
810a4dd8e0756a715c6248a78c2d29a8c0985841243eb436318a234145847c29

見つけてきたtxidをパスワードとして、ARC4でデコードします。
1c434e5452505254590000000000000000039ff05d000000000bebc200000000000000000000000000000000000000000000000000000000000000000000

  • 不要なデータ?
はじめのbyteは無視します。
1c

  • CNTRPRTY
次の8byteは「CNTRPRTY」という文字列を16進コード文字列に変換したもので、Counterpartyのトランザクションには必ずある決まり文句です。
434e545250525459

  • トランザクションタイプ
次の4byteがトランザクションタイプです。
00000000

0はSENDを指します。

  • Asset_ID
次の8byteはAsset_IDです。
00000000039ff05d

10進数に直すと60813405で、前回と同じように変換します。

60813405 / 26 = 2338977 余り 3 D
2338977 / 26 = 89960 余り 17 R
89960 / 26 = 3460 余り 0 A
3460 / 26 = 133 余り 2 C
133 / 26 = 5 余り 3 D
5 / 26 = 0 余り 5 F

出来上がったDRACDFを反転したものがAsset名になります。

FDCARD

  • Quantity
次の8byteはQuantityです。
000000000bebc200

10進数に直すと200000000で単位がsatoshiなので、2FDCARDになります。


意外とシンプルな構造になっているようです。


Counterpartyのトランザクションを解読してみる(Issuance)


Counterpartyの勉強のために、Counterpartyのトランザクションを読んでみます。

今回の題材はSpells of Genesis関連のトランザクションの中から選択した、独自トークンの発行を行うIssuanceというトランザクションタイプです。

エンコード方法は何パターンかあるようなのですが、今回は以下のmultisigを使ったものを選択しました。

https://www.blockscan.com/txInfo/11603601

  • データの準備
まずはvoutの一つ目のscriptPubKeyから以下を抜き出します。

OP_1
02a8d23b56ec9dfad3ffd52f83ac86bef27fc6b11775fec2f682a7d16e31c0272e
03a5998b49075640bd89ff6f0e849b693d99aafbf1145eea9bb2e1f2753fa4e3da
02076aee3ebdd93955b70ee616de7867aa451a990ad53e0eaad0893626f231721d
OP_3
OP_CHECKMULTISIG


2行目から4行目に公開鍵の様なものが3つありますが、この内、最初の2つはダミーで、Counterpartyで利用するデータが格納されています。

2つのダミー公開鍵を、それぞれ前後1byteずつ削って結合させます。
a8d23b56ec9dfad3ffd52f83ac86bef27fc6b11775fec2f682a7d16e31c027a5998b49075640bd89ff6f0e849b693d99aafbf1145eea9bb2e1f2753fa4e3

これは暗号化されているので、複合化するためにvinの最初のtxidを探してきます。
24b10dd846507572b21e15a78c990cf7f993f13ec8623ca738cf9fe1f12ddf99

見つけてきたtxidをパスワードとして、ARC4でデコードします。
これがCounterparty用の生データです。
3d434e5452505254590000001400000000039ff05d00000006fc23ac0001000000000000000000265370656c6c73206f662047656e657369732f4d6f6f6e

  • 不要なデータ?
はじめのbyteは無視します。
3d

  • CNTRPRTY
次の8byteは「CNTRPRTY」という文字列を16進コード文字列に変換したもので、Counterpartyのトランザクションには必ずある決まり文句です。
434e545250525459

  • トランザクションタイプ
次の4byteがトランザクションタイプです。
00000014

0x14=20はISSUANCEを指します。

  • Asset_ID
次の8byteはAsset_IDです。
00000000039ff05d

10進数に直すと60813405で、これが1ならXCPで、1ではない場合は以下のように変換します。

まず、AからZまでが入った配列を用意し、ここからindexが0ならA、1ならBという具合にアルファベットを取り出します。

IDを26で割った余りがindex、余りを切り捨てた整数値が次の計算用データとなり、次の計算用データが0になるまで繰り返します。

60813405 / 26 = 2338977 余り 3 D
2338977 / 26 = 89960 余り 17 R
89960 / 26 = 3460 余り 0 A
3460 / 26 = 133 余り 2 C
133 / 26 = 5 余り 3 D
5 / 26 = 0 余り 5 F

出来上がったDRACDFを反転したものがAsset名になります。

FDCARD

  • Quantity
次の8byteはQuantityです。
00000006fc23ac00

10進数に直すと30000000000で単位がsatoshiなので、300FDCARDになります。

  • Description
Quantityの後ろには簡単な説明文的なものを埋め込めるようです。
01000000000000000000265370656c6c73206f662047656e657369732f4d6f6f6e

内、頭の11byteは文字数などが入っているようなので、それ以降を文字列に変換します。
Spells of Genesis/Moon

なお、文字数が足りないときはvoutを2つ持たせて、追加のDescriptionデータに使えるようです。

OP_1 038dd23b56ec9dfad3ffb24ea3dbe7cc965fa3419508b88eb2c75bf2c231c127a6 02a5998b49075640bdafac1f6be8f71a1df6ccdbb671308fe8db92dd3850cb8d4f 02076aee3ebdd93955b70ee616de7867aa451a990ad53e0eaad0893626f231721d OP_3 OP_CHECKMULTISIG

18434e5452505254596761206361726420666f7220464c444300000000000000000000000000000000000000000000000000000000000000000000000000

2つ目のデータを同じ様にデコードし、本来トランザクションタイプが入る10byte以降の部分を文字列に変換すると以下になります。

ga card for FLDC


一つ目のDescriptionと連結するとDescriptionが完成します。

Spells of Genesis/Moonga card for FLDC


以下ブロックエクスプローラのAsset Infoを確認すると、Descriptionが正しくデコードされていることが分かります。
https://www.blockscan.com/assetInfo/FDCARD


これで基本的なCounterpartyのトランザクションデータの作りが分かりました。
multisig以外のエンコード方法や、LOCKのトランザクションについては、また別の機会に検証してみたいと思います。


2015年8月10日月曜日

Monacoinでマルチシグネチャアドレスを作成する


Counterpartyについて調べていたところ、マルチシグネチャという単語が出てきました。

また、Spells of Genesisでも資金持ち逃げ、使い込みなどの防止に、マルチシグネチャを利用しているようです。

ということで、今後、暗号通貨界隈の常識になりそうなマルチシグネチャについて、Monacoinでも動くのかを試してみます。

具体的には、2-of-3マルチシグネチャアドレスを作成し、送金してみます。
3つの署名のうち、2つあれば出金できるというヤツですね。

  • アドレスの作成
getnewaddressで署名するためのアドレスを3つ作成します。

後で確認しやすいようにアカウントを設定しておきます。
$ monacoind getnewaddress  "monamultisig"
MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm

$ monacoind getnewaddress  "monamultisig"
MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83

$ monacoind getnewaddress  "monamultisig"
MVe4ucgqSPRkR1yescuuX17D7t5vcikznd

getaddressesbyaccountでちゃんと作成できているか確認します。
$ monacoind getaddressesbyaccount "monamultisig"
[
    "MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83",
    "MVe4ucgqSPRkR1yescuuX17D7t5vcikznd",
    "MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm"
]

  • 公開鍵の確認
validateaddressで各アドレスの公開鍵を確認します。
monacoind validateaddress MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83
{
    "isvalid" : true,
    "address" : "MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83",
    "ismine" : true,
    "isscript" : false,
    "pubkey" : "020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a",
    "iscompressed" : true,
    "account" : "monamultisig"
}

monacoind validateaddress MVe4ucgqSPRkR1yescuuX17D7t5vcikznd
{
    "isvalid" : true,
    "address" : "MVe4ucgqSPRkR1yescuuX17D7t5vcikznd",
    "ismine" : true,
    "isscript" : false,
    "pubkey" : "03e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca",
    "iscompressed" : true,
    "account" : "monamultisig"
}

monacoind validateaddress MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm
{
    "isvalid" : true,
    "address" : "MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm",
    "ismine" : true,
    "isscript" : false,
    "pubkey" : "035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb438",
    "iscompressed" : true,
    "account" : "monamultisig"
}

ちなみに他人のアドレスに対してvalidateaddressを実行しても、結果には公開鍵は含まれず、有効であることだけ分かります。
monacoind validateaddress MR2j3u5oNWZzAAxeHtSinHUEVNiSbkn8zJ
{
    "isvalid" : true,
    "address" : "MR2j3u5oNWZzAAxeHtSinHUEVNiSbkn8zJ",
    "ismine" : false
}

  • マルチシグネチャアドレスの作成
createmultisigを使ってマルチシグネチャアドレスを作成します。

先ほど作成したアドレスの公開鍵を利用して作成し、一番目の引数が出金時に必要な署名の数です。
$ monacoind createmultisig 2 '["020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a", "03e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca", "035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb438"]'
{
    "address" : "3BUVYHpNB6J9X4GsXUo51d7UkfM322SspM",
    "redeemScript" : "5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"
}

redeemScriptは後で使います。

ちなみに公開鍵ではなく、アドレスからも作れるようで、以下のように同じ結果になります。
$ monacoind createmultisig 2 '["MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83", "MVe4ucgqSPRkR1yescuuX17D7t5vcikznd", "MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm"]'
{
    "address" : "3BUVYHpNB6J9X4GsXUo51d7UkfM322SspM",
    "redeemScript" : "5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"
}

  • マルチシグネチャアドレスへの送金
先ほど作成したアドレス宛に、sendtoaddressで送金してみます。
$ monacoind sendtoaddress 3BUVYHpNB6J9X4GsXUo51d7UkfM322SspM 0.1
64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac


ブロックエクスプローラを見ると、しっかりと着金しています。

  • マルチシグネチャアドレス宛送金トランザクションの確認
getrawtransactionでトランザクションの中身を確認します。
オプションに1をつけるとrawではなくデコード済みが返ってきます。
$ monacoind getrawtransaction 64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac 1
{
    "hex" : "0100000002ad378b21681f50fbc3cf3cc1dc5967b003733437bd0008a7bc52e44acd2ec3ce00000000fdfe0000493046022100fcdca98debaa9413cba58f2c6ef22277797e6448ee1d391d840a295cc8d65614022100ccc415a475c0a0e4d88f41e4b53f59692a7847951d171cd723f7bb803d4748f301473044022030dbe7bb6fa1271bb2dea33341497847786a74d96f8d21286f5f279ade8b9e6302207be36c17cb83e2dc441ff37ea68ce0272c5b271635de2d82c1c812c762e762b6014c6952210336e6fc63f48676949b646456adf96b1183f296bd0e700bd894174f524839798a2102ea9fa819e3e9c05580671a095ce73aee766ce39c55e084ae5ad50355a5f1d4ef2103fe8b5f8df85b57ee876c3cb4ea23dfe7063ad6fb3500c7527971c80d1c931def53aeffffffff0221976a0390f96d87a81a7941c6279fb233625f44d17dc040f594f4fd6f99d2010000006c493046022100856d3d4813fc636873ab43d13e8251ccd3e4c8e29d284d7812d2537ecf0550dd022100af0a2139aa266e52619a1ed716ebdba6239c6f4537fe3a78e4d9db6f1a1883820121020c61b05ce77a8b0d36c813d882f7e730158296c9e5ef4184ce2f30f4acab5cfcffffffff026f0a1700000000001976a9141391189829986705d972aa1b0c26de994582afb588ac809698000000000017a9146b52e73d9d62dd84f9c974c0989ebc186d90531f8700000000",
    "txid" : "64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac",
    "version" : 1,
    "locktime" : 0,
    "vin" : [
        {
            "txid" : "cec32ecd4ae452bca70800bd37347303b06759dcc13ccfc3fb501f68218b37ad",
            "vout" : 0,
            "scriptSig" : {
                "asm" : "0 3046022100fcdca98debaa9413cba58f2c6ef22277797e6448ee1d391d840a295cc8d65614022100ccc415a475c0a0e4d88f41e4b53f59692a7847951d171cd723f7bb803d4748f301 3044022030dbe7bb6fa1271bb2dea33341497847786a74d96f8d21286f5f279ade8b9e6302207be36c17cb83e2dc441ff37ea68ce0272c5b271635de2d82c1c812c762e762b601 52210336e6fc63f48676949b646456adf96b1183f296bd0e700bd894174f524839798a2102ea9fa819e3e9c05580671a095ce73aee766ce39c55e084ae5ad50355a5f1d4ef2103fe8b5f8df85b57ee876c3cb4ea23dfe7063ad6fb3500c7527971c80d1c931def53ae",
                "hex" : "00493046022100fcdca98debaa9413cba58f2c6ef22277797e6448ee1d391d840a295cc8d65614022100ccc415a475c0a0e4d88f41e4b53f59692a7847951d171cd723f7bb803d4748f301473044022030dbe7bb6fa1271bb2dea33341497847786a74d96f8d21286f5f279ade8b9e6302207be36c17cb83e2dc441ff37ea68ce0272c5b271635de2d82c1c812c762e762b6014c6952210336e6fc63f48676949b646456adf96b1183f296bd0e700bd894174f524839798a2102ea9fa819e3e9c05580671a095ce73aee766ce39c55e084ae5ad50355a5f1d4ef2103fe8b5f8df85b57ee876c3cb4ea23dfe7063ad6fb3500c7527971c80d1c931def53ae"
            },
            "sequence" : 4294967295
        },
        {
            "txid" : "d2996ffdf494f540c07dd1445f6233b29f27c641791aa8876df990036a972102",
            "vout" : 1,
            "scriptSig" : {
                "asm" : "3046022100856d3d4813fc636873ab43d13e8251ccd3e4c8e29d284d7812d2537ecf0550dd022100af0a2139aa266e52619a1ed716ebdba6239c6f4537fe3a78e4d9db6f1a18838201 020c61b05ce77a8b0d36c813d882f7e730158296c9e5ef4184ce2f30f4acab5cfc",
                "hex" : "493046022100856d3d4813fc636873ab43d13e8251ccd3e4c8e29d284d7812d2537ecf0550dd022100af0a2139aa266e52619a1ed716ebdba6239c6f4537fe3a78e4d9db6f1a1883820121020c61b05ce77a8b0d36c813d882f7e730158296c9e5ef4184ce2f30f4acab5cfc"
            },
            "sequence" : 4294967295
        }
    ],
    "vout" : [
        {
            "value" : 0.01509999,
            "n" : 0,
            "scriptPubKey" : {
                "asm" : "OP_DUP OP_HASH160 1391189829986705d972aa1b0c26de994582afb5 OP_EQUALVERIFY OP_CHECKSIG",
                "hex" : "76a9141391189829986705d972aa1b0c26de994582afb588ac",
                "reqSigs" : 1,
                "type" : "pubkeyhash",
                "addresses" : [
                    "M9gctXWKs8hSrRbdJMRR4t9s6Yc8yHKd2s"
                ]
            }
        },
        {
            "value" : 0.10000000,
            "n" : 1,
            "scriptPubKey" : {
                "asm" : "OP_HASH160 6b52e73d9d62dd84f9c974c0989ebc186d90531f OP_EQUAL",
                "hex" : "a9146b52e73d9d62dd84f9c974c0989ebc186d90531f87",
                "reqSigs" : 1,
                "type" : "scripthash",
                "addresses" : [
                    "3BUVYHpNB6J9X4GsXUo51d7UkfM322SspM"
                ]
            }
        }
    ]
}

  • 秘密鍵の確認
dumpprivkeyで各アドレスの秘密鍵を確認します。

結果はマスクしてあります。
$ monacoind dumpprivkey MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83
xxxxprivateKey1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
noojloon@noojloon-MS-7821:~$ monacoind dumpprivkey MVe4ucgqSPRkR1yescuuX17D7t5vcikznd
xxxxprivateKey2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
noojloon@noojloon-MS-7821:~$ monacoind dumpprivkey MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm
xxxxprivateKey3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

  • マルチシグネチャアドレスからの送金トランザクションを作成
createrawtransactionで送金トランザクションを作成します。

マルチシグネチャアドレス宛送金のtxidと、getrawtransactionで確認したデータの中身から、voutの大きいほうのnとそのscriptPubKeyhexを利用します。
$ monacoind createrawtransaction '[{"txid":"64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac","vout":1,"scriptPubKey":"a9146b52e73d9d62dd84f9c974c0989ebc186d90531f87","redeemScript":"5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"}]' '{"MR2j3u5oNWZzAAxeHtSinHUEVNiSbkn8zJ":0.05}'
0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae8640100000000ffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000

この辺は普段の処理と比べるとややローレベルな処理になるため、今まで自動的に処理されていたことも自分でやる必要があります。

この例でいうとお釣りについて一切考えずに処理しているため、0.10の入金に対して0.05の出金ということになり、差額は手数料として消えるようです。

fee0.001にするには、以下のようにお釣り用のアドレスも送金先に指定します。
{"あて先":0.05,"お釣り用":0.049}

  • 送金トランザクションに署名する
signrawtransactionで先ほど作成したトランザクションに署名します。
$ monacoind signrawtransaction '0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae8640100000000ffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000' '[{"txid":"64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac","vout":1,"scriptPubKey":"a9146b52e73d9d62dd84f9c974c0989ebc186d90531f87","redeemScript":"5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"}]' '["xxxxprivateKey1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]'
{
    "hex" : "0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae86401000000b500483045022006265235010d34980be0054469137987a059db6b18803d2310195d24b0fac295022100a6b6c349b6378ed4ea9b9443bbcde4d2673d42f8b3bb108669e759940e1ca243014c695221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853aeffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000",
    "complete" : false
}

一つではコンプリートしないので、もう一つ署名します。
$ monacoind signrawtransaction '0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae86401000000b500483045022006265235010d34980be0054469137987a059db6b18803d2310195d24b0fac295022100a6b6c349b6378ed4ea9b9443bbcde4d2673d42f8b3bb108669e759940e1ca243014c695221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853aeffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000' '[{"txid":"64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac","vout":1,"scriptPubKey":"a9146b52e73d9d62dd84f9c974c0989ebc186d90531f87","redeemScript":"5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"}]' '["xxxxprivateKey2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]'
{
    "hex" : "0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae86401000000fdff0000483045022006265235010d34980be0054469137987a059db6b18803d2310195d24b0fac295022100a6b6c349b6378ed4ea9b9443bbcde4d2673d42f8b3bb108669e759940e1ca24301493046022100e6b0a31018d371bd52bdbb5047c8a2d4978404cd300b51edbdcdd84eefd5eb54022100c20925bac763d3ebf706d0481c52f70ca49aa951086b8df1c1bf6e8780f380a2014c695221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853aeffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000",
    "complete" : true
}

コンプリートしました。

  • トランザクションの送信
二つの署名が終わったトランザクションを、sendrawtransactionで実際に送信します。
$ monacoind sendrawtransaction 0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae86401000000fdff0000483045022006265235010d34980be0054469137987a059db6b18803d2310195d24b0fac295022100a6b6c349b6378ed4ea9b9443bbcde4d2673d42f8b3bb108669e759940e1ca24301493046022100e6b0a31018d371bd52bdbb5047c8a2d4978404cd300b51edbdcdd84eefd5eb54022100c20925bac763d3ebf706d0481c52f70ca49aa951086b8df1c1bf6e8780f380a2014c695221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853aeffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000
4095c488fe886b66560540fb953cab44e2ab36f70c40fc5cc9e169f1e26e459b


半分手数料で持っていかれましたが、無事出金できました。


2015年4月16日木曜日

Navigation DrawerとFragmentをAPI level 11未満で使いたい


ちょっといまさらですが、NavigationDrawerについて調べてみました。

NavigationDrawerを実装するには、サポートライブラリのandroid.support.v4.widget.DrawerLayoutを使います。

NavigationDrawerに限らず、画面を切り替えたりするのにFragmentを使うと便利なのですが、API level 11以上です。

API level11というとAndroid 3.0以降ということになりますが、Android 2.3.3 - 2.3.7がまだ1割弱は現存しているため、割り切ってしまうのもどうかなぁという気もします。

そこで方法がないか調べてみたところ、同じくサポートライブラリのandroid.support.v4.app.Fragmentを使えば可能でした。


ということで今回は、android.support.v4.widget.DrawerLayoutとandroid.support.v4.app.Fragmentを使って、NavigationDrawerを実装してみます。

NavigationDrawerから設定画面と詳細設定画面を切り替えるサンプルを作成します。

まずは、パーツを作成します。
設定画面と詳細設定画面のFragment用のレイアウトをそれぞれ作成します。

fragment_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content_settings"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Settings"
        android:id="@+id/textView1" />
</LinearLayout>

fragment_advanced_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content_advanced_settings"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Advanced Settings"
        android:id="@+id/textView2" />
</LinearLayout>

何の変哲も無いレイアウトです。

次に、android.support.v4.app.Fragmentを継承したクラスをそれぞれ作成します。

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class SettingsFragment extends Fragment{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_settings, container, false);
    }
}

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class AdvancedSettingsFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_advanced_settings, container, false);
    }
}

onCreateViewメソッドをオーバーライドし、先ほどのxmlレイアウトファイルをそれぞれ紐付けます。

これでパーツは出来たので、次にメイン画面を作成します。

activity_main.xml

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </FrameLayout>

    <LinearLayout
        android:id="@+id/drawer"
        android:orientation="vertical"
        android:layout_width="320dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="#ffffffff">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Menu1"
            android:id="@+id/menuText1"
            android:textColor="#ff505050" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Menu2"
            android:id="@+id/menuText2"
            android:textColor="#ff505050" />
    </LinearLayout>

</android.support.v4.widget.DrawerLayout>

android.support.v4.widget.DrawerLayoutをルートに配置し、一つ目の子がコンテンツ用、二つ目の子がNavigationDrawer用です。

コンテンツはFragmentを使って実装し、NavigationDrawerは今回はそのままここに書いてしまいます。

それでは組み立てます。

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBarDrawerToggle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

public class ActivityMain extends ActionBarActivity {

    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initDrawer();
        initDrawerMenu();

        changeFragment(new SettingsFragment());
    }

    private void initDrawer() {
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.app_name, R.string.app_name);
        mDrawerToggle.setDrawerIndicatorEnabled(true);
        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_drawer);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
    }

    private void initDrawerMenu() {
        TextView menu1 = (TextView) findViewById(R.id.menuText1);
        menu1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changeFragment(new SettingsFragment());
                setTitle("Settings");
            }
        });
        TextView menu2 = (TextView) findViewById(R.id.menuText2);
        menu2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changeFragment(new AdvancedSettingsFragment());
                setTitle("Advanced Settings");
            }
        });
    }

    private void changeFragment(Fragment f) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction()
                .replace(R.id.content_frame, f)
                .commit();
        mDrawerLayout.closeDrawers();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (mDrawerToggle.onOptionsItemSelected(item)) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

今時っぽくするためにActionBarを使いたいので、またもやサポートライブラリからActionBarActivityを継承してクラスを作成します。

initDrawer()メソッドの中で、NavigationDrawerとActionBarの設定を行っています。

getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_drawer)で左上の三本線のアイコンを設定しています。
これ、なぜだか分かりませんが、あんまりネットで参考になる日本語サイトがなく、結構ハマりました。

アイコン画像はここで拾って来て、drawableに放り込んでおきます。

アイコンからNavigationDrawerを呼べるように、onOptionsItemSelected()内でActionBarDrawerToggleのonOptionsItemSelected()を呼びます。

initDrawerMenu()メソッドの中で、NavigationDrawerのメニューの設定を行っています。

今回はそれぞれOnClickListenerを設定し、コンテンツの変更とついでにActionBarのタイトルの変更を行っています。


実際のコンテンツの変更については、changeFragment()メソッドの中で実装しています。

FragmentManagerを利用してコンテンツを変更し、NavigationDrawerをクローズする処理を行っています。


出来ました。

まぁメニュー二つとかそんなシンプルな構成であれば、NavigationDrawerを使う必要もないのですが、とりあえず最近のアプリっぽくなりました。

2015年3月10日火曜日

IngressのGlyph Hackを自動化する ~Input Subsystem~


お蔵入り寸前だったGlyph Hackの自動化ですが、Translaterメダルやら、ハッキングボーナスやスピードボーナスの上限解放やら、なんとかモチベーションを持ち直すニュースがあったので、再チャレンジします。

ということで、今回はInput Subsystemとやらを調べてみました。

まず、Input Subsystemで利用するイベントデバイスファイルは/dev/input/にあるとのことなので確認してみます。

自分の環境では、/dev/input/配下に以下が格納されていました。

event0
event1
event2
event3
event4
mice

それぞれが何らかのデバイスに対応しているそうですが、これだけでは分からないので、BusyBoxをインストールして、hexdumpコマンドで覗いてみます。

# /system/xbin/hexdump /dev/input/event0
0000000 1d21 0003 4cbf 0004 0003 0039 69d4 0000
0000010 1d21 0003 4cbf 0004 0003 0035 023c 0000
0000020 1d21 0003 4cbf 0004 0003 0036 028a 0000
0000030 1d21 0003 4cbf 0004 0003 003a 004a 0000
0000040 1d21 0003 4cbf 0004 0003 0030 0006 0000
0000050 1d21 0003 4cbf 0004 0000 0000 0000 0000
0000060 1d21 0003 8987 0004 0003 003a 0018 0000
0000070 1d21 0003 8987 0004 0003 0030 0002 0000
0000080 1d21 0003 8987 0004 0000 0000 0000 0000
0000090 1d21 0003 c0fa 0004 0003 0039 ffff ffff
00000a0 1d21 0003 c0fa 0004 0000 0000 0000 0000

なにやら出力されてきました。
しかし、何かやろうとすると、毎回バイナリを解読させられる運命にあるようですね。

ざっと画面をタッチしたりしながら眺めていると、はじめの2byteはイベントが発生した時間のようです。

次の2byteは入力のタイプEV_ABSを表す3かと思いましたが、なんだか違いそうです。

次の2byteもx軸かy軸かを表すもの、、、じゃなさそうです。
座標は後ろから2番目の2byteのようですね。
うーん。。分からない。

ということでもうちょっと良い方法がないか調べたところありました。
これはBusyBoxのインストールも不要です。

# getevent /dev/input/event0
0003 0039 00006be2
0003 0035 0000029d
0003 0036 000002aa
0003 003a 0000004d
0003 0030 00000008
0000 0000 00000000
0003 003a 00000037
0003 0030 00000006
0000 0000 00000000
0003 0039 ffffffff
0000 0000 00000000

type、code、valueってことなんだろうけど、なんか分かりそうで分からない。

さらに色々調べたところ、geteventコマンドに素敵なオプションを発見。

# getevent -lt /dev/input/event0
[  320377.729707] EV_ABS       ABS_MT_TRACKING_ID   00009411
[  320377.729707] EV_ABS       ABS_MT_POSITION_X    00000296
[  320377.729707] EV_ABS       ABS_MT_POSITION_Y    00000348
[  320377.729707] EV_ABS       ABS_MT_PRESSURE      00000048
[  320377.729707] EV_ABS       ABS_MT_TOUCH_MAJOR   00000007
[  320377.729707] EV_SYN       SYN_REPORT           00000000
[  320377.766317] EV_ABS       ABS_MT_TRACKING_ID   ffffffff
[  320377.766317] EV_SYN       SYN_REPORT           00000000

これなら読み解こうという気になります。

では、まずは登場人物を整理します。

type
EV_ABS 3EV_SYN 0

code
SYN_REPORT 0
ABS_MT_TRACKING_ID 57
ABS_MT_POSITION_X 53
ABS_MT_POSITION_Y 54
ABS_MT_PRESSURE 58
ABS_MT_TOUCH_MAJOR 48

value
ABS_MT_TRACKING_IDのIDや、ABS_MT_POSITION_Xのx座標など。

次に、タッチイベントを発生させるための流れを整理します。

まずEV_ABSABS_MT_TRACKING_IDで一連のイベントのIDを設定します。
Android側の動きをgeteventで見ていると、1ずつインクリメントされ連番になっているようです。
ただ、適当な値でも特に文句は言われませんので、乱数とかでも大丈夫なのかも??

次に座標を送信します。
ABS_MT_POSITION_Xと、ABS_MT_POSITION_Yでイベントが発生した座標を設定します。
続いてABS_MT_PRESSUREで圧力、ABS_MT_TOUCH_MAJORで断面積ということで、いまいち良く分かりませんが設定します。
最後に、EV_SYNSYN_REPORTを送信します。
ここまでが座標送信のワンセットです。

座標、ABS_MT_PRESSUREABS_MT_TOUCH_MAJORSYN_REPORTのセットを連続で複数送信するとスワイプイベントになります。

イベント終了時にはABS_MT_TRACKING_IDにFFFFFFFFを設定して送信し、EV_SYNSYN_REPORTを送信して完了です。


ではやってみましょう。

# sendevent /dev/input/event0 3 57 3800
# sendevent /dev/input/event0 3 53 1222
# sendevent /dev/input/event0 3 54 1237
# sendevent /dev/input/event0 3 58 72
# sendevent /dev/input/event0 3 48 7
# sendevent /dev/input/event0 0 0 0
# sendevent /dev/input/event0 3 57 -1
# sendevent /dev/input/event0 0 0 0

ちゃんと動きました。

さらに複数座標を送信して、動きをつけて軌跡を描けるか試しました。

成功!ちなみに描いたのは「SUCCESS」です。
なぜこんなに滑らかな曲線になるのかは謎です。


しかし、rootとってshellからなら何でも出来てしまうってのは結構恐ろしい事だなぁ、と。
Google先生が練り上げたAndroidのセキュリティの壁も、いとも簡単に超えてしまうわけですね。

まぁ当然なんですが。

とりあえずroot系アプリ入れるときは、ちょっと注意したほうがいいなと思いましたとさ。

2015年2月16日月曜日

IngressのGlyph Hackをハックする


IngressのGlyph Hackをハック中。

フレームバッファのバイナリをBitmapに変換して必要なとこだけキャプチャするのは完了。
こうして人の脳は退化していくわけですね。

ついでに自前の検出器を作成して、それがどの図柄なのかを検出するのも完了。
画面上の表示位置が変わらないし図柄がすごく単純なので、OpenCVなんて使う必要は全くありませんでした。

そしてあとは自動で絵を描くだけだ~と思ったら、後一歩のところで壁にぶつかりました。

そもそも、画面キャプチャ、検出器作成まで出来たら、あとはイージーだと思って何も調べてなかったのですが、結構厄介な問題がありました。

よそ様のアプリを操作するためにタッチイベントを生成するには、android.permission.INJECT_EVENTSが必要なのですが、こいつがいわゆるシステム権限らしくうまく動きません。

AndroidManifest.xmlで宣言しても、SecurityExceptionが発生します。


システム権限ということであれば、/system/app配下に放り込めば良いのかと考え、以下のようにしてADB Shellから無理やり放り込んでシステムアプリ化してみました。

$ adb shell
$ su
# mount -o rw,remount /system
# cp /sdcard/AutoGlyph.apk /system/app
# chmod 644 /system/app/AutoGlyph.apk
# reboot

が、やはり動きません。


INJECT_EVENTSprotectionLevelsignatureで、公式の説明にはthe application that declared the permissionと同じ証明書で署名されていないとダメだとあります。
googleの署名じゃないとダメってことでしょうか?

もしかするとprotectionLevelsignatureOrSystemであれば、システムアプリ化するだけでいけたのかもしれませんが。。

これはお蔵入りの予感。。


ただ、LMT Launcherとかは普通に動いてるわけで、何かやり方はあるんでしょう。
セキュリティ的には出来ちゃダメな気もしますが。

システムアプリを偽装できるのか、Input Subsystemあたりを利用するのか。
LMT Launcherのpackagenamecom.android.lmtなのが気になりますが、それだけではダメな気がします。

う~ん。。


2015年2月15日日曜日

Androidアプリからスクリーンショット /system/bin/screencap


前回に続きスクリーンショットについてです。

/system/bin/screencap -p test.png

前回は上記のように-pオプションを付けて実行し、ファイルの拡張子も.pngにしていたので、pngファイルで保存されていました。

普通の用途ではそれでいいんだと思いますが、今回は訳あって1200ミリ秒ぐらいの中で全部やりたいので、時間的にギリギリです。

そこで、pngへの変換を省いて生のBitmap画像を得る方法について実験します。


/system/bin/screencapコマンドについては、pオプションやファイル名を指定せずに実行すると、いわゆるフレームバッファの生データが標準出力に出力されます。

データのはじめにはwidth、height、formatの各4byteで12byteのヘッダが付いてくるそうです。

とりあえずヘッダの中身を見てみます。

 2684682240
 655360
 16777216

うーん、まったく意味が分からない。。
実際のwidthやheightで割ってみても意味のありそうな数字になりません。

LITTLE_ENDIANで読み直してみます。

 1440
 2560
 1

これだ!

そしてその後には14,745,600byteのデータが飛んできました。

Nexus6で実験しているので、画面サイズは縦×横が2,560×1,440です。

2560 * 1440 = 3686400

14745600 / 3686400 = 4

1ピクセルあたり4byteということは、普通に考えれば32bitということでしょうか。
ただ、ネットで調べると16bitのダブルバッファリングだという説があったので、適当に16bitのヘッダを付加して表示してみます。

はい。ぜんぜんダメでした。

素直に32bitのヘッダを付加してみます。

上下が逆転しています。

Bitmapのデータはなぜか左下から右方向に向かって格納されているそうなのですが、フレームバッファには普通に並んでいるので、byte配列を逆さにしてからBitmapに食わせます。

色情報の順番も変わってしまいました。また、左右は入れ替えなくて良いようです。

行単位で逆にします。

まだちょっと色がおかしい。
どうやらRとBが逆になっているようですね。

このサンプルだと分かりにくいのでもう少しカラフルなサンプルでRとBを入れ替えると。

完成。


正体の良く分からないバイナリをいい感じに加工して読めるデータにする作業って、何か高度なパズルを解いているようで楽しいです。

まぁちゃんと調べれば正体は分かるんでしょうけど。。


2015年2月8日日曜日

Androidアプリからスクリーンショット /system/bin/screencap -p


Androidアプリからスクリーンショットを取るには、JNIとかでフレームバッファを直接見に行くしかないのかと思っていたのですが、Android4.0から/system/bin/screencapでスクリーンショットが可能になったとのことです。

root化は必要なものの、ずいぶん楽になったようですね。

try {
    final String SAVE_DIR = "/test/";
    String fileName = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + ".png";
    String fullpath = Environment.getExternalStorageDirectory().getPath() + SAVE_DIR + fileName;
    Process p = Runtime.getRuntime().exec("su");
    OutputStream os = p.getOutputStream();
    os.write(("/system/bin/screencap -p " + fullpath).getBytes("ASCII"));
    os.flush();
    os.close();
    p.waitFor();
    p.destroy();
} catch (IOException e) {
    e.printStackTrace();
} catch (InterruptedException e) {
    e.printStackTrace();
}

これ終わったらちゃんとdestroy()で開放してあげないと、色々と後で死にそうですね。