CoinomiWalletが何故秘密鍵の流出をしてしまったか雑に検証する

f:id:serinuntius:20190228163123p:plain

事の発端

このツイートにより、GoogleのスペルチェックAPIによってニーモニックが流出してしまっているとのこと。

なぜそんなことが起こったか気になったので調べてみる

下記のCoinomiWalletの公式サイトからMac版をダウンロードする

www.coinomi.com

v1.0.5*1を本記事では検証しました。

f:id:serinuntius:20190228153426p:plain

Windows, Mac, Linuxと対応していたので、Electronを使っているのではないかと当たりをつけてました。

f:id:serinuntius:20190228153900p:plain

右クリック→パッケージの内容を表示 とすると、アプリのディレクトリが見れます。

f:id:serinuntius:20190228154130p:plain

ElectronではなくJavaだった!

coinomi-wallet-mac.jar をデコンパイルする

Javaだったのでデコンパイルする必要があります。

下記のデコンパイラを使用します。

jd.benow.ca

f:id:serinuntius:20190228154546p:plain

f:id:serinuntius:20190228154637p:plain

f:id:serinuntius:20190228154901p:plain chromium(Google Chromeのベース部分)で動いてそうなのが伺えます。

AppWindow.class を確認してみると SpellCheckerService というのが利用されてますが、 setEnabled(false) とスペルチェックをオフにしています。 OAuthWindow.class も同様にoffにされています。

f:id:serinuntius:20190228155357p:plain

あれ、offにされてるのに送信されるの?バグなの??

ここで冷静になる

Twitterで報告されるような脆弱性対応終わってないわけないでしょ。修正済みなのでは?」

と思って、公式Twitter見てみたら・・・

公式声明が出てました。

そらそうですよね。

公式声明まとめ

  • restore時のみスペルチェックが発動してもれる
  • GoogleAPIAPIキーが含まれておらず、正常には処理できていなかった
  • モバイルは大丈夫
  • もう修正済みで自動アプデされる

chromiumとjxBrowser

jxBrowserというjavachromiumラッパー(?)がちょうど1週間前にアップデートされています。

f:id:serinuntius:20190228160818p:plain

jxbrowser.support.teamdev.com

chromiumがデフォルトでGoogleAPIを利用して通信してしまうので、それをオフにできるような修正が入っていました。 セキュリティ要件等で厳しい時は、それをオフにするといいとのこと。

まとめ

  • CoinomiWalletのデスクトップ版のみこのバグがあったが既に修正済み(自動でアプデ配信されているようです)
  • chromium便利だけど、こういう時びっくり挙動があったりでびっくりする(かも)

*1:checksum: 39efb726c8993af435710be6fda4c9ab808f39b7601951168d3536979be649c0

恥ずかしながら私はPromiseのthenとcatchの順番に意味があるとは知りませんでした

どういうこと?

catch => thenで書いたときとthen => catchで書いたときの挙動が異なります。

次のコードを用意しました。

ボタンを左から順番に押してみるとどういうことかわかると思います。

See the Pen then catchの順番によって意図しないコードになる by serinuntius (@serinuntius) on CodePen.

そうです。最後のボタンを押したときだけ2回alertが出ましたね。

最後のボタンは catch => thenの順序で描いて、条件的にcatchに入るパターン です。

使い分けると便利?かもしれません。おそらく大半のケースで then => catch という順序で書いた方が意図する実行結果になるということがわかりました。

今回はたまたまFirebaseのドキュメントを見ながら書いていて、catchが書いてあったからそこにthenを足したことで気づきましたw

Failed to load gRPC binary module because it was not installed for the current systemで怒られたときの対処法

なかなか解決しなかったので一応メモ。

環境

node: 10系

使用パッケージ

  • electron
  • firebase

対処法

1. nodeを9系にダウングレードする(あまり意味ないかも)

僕は ndenv でnodeをインストールしているので

$ ndenv install -l |less
$ ndenv install v9.11.2
$ ndenv global v9.11.2

node_modulesを吹っ飛ばす

$ rm -rf node_modules
$ npm i

これでもまだ僕の場合駄目でした。

2. rebuildする

$ npm rebuild --runtime=electron --target=2.0.11 --disturl=https://atom.io/download/electron --abi=57

これで僕の場合解決しました。

参考

stackoverflow.com

Q. ReactでHTMLをエスケープせずに突っ込みたいときは?

A

      <div dangerouslySetInnerHTML={{ __html: '<a href="/hoge">HOGE</a>' }} ></div>

もちろん __htmlのところは変数でも大丈夫。

参考

stackoverflow.com

新卒で入社して1年半ぐらい勤めた面白法人Kayacを退職しました

はじめに

この記事は ex-KAYAC Advent Calendar 2018 の15日目の記事です。

おそらくこのアドベントカレンダーの中で僕は最新の辞めた人ですね。今までと今後について書きたいと思います。

今まで

受託チーム(CL)時代

プラコレという、結婚式をしたいカップルと結婚式場をマッチングさせてチャットで予約できるサービスや、両家のレシピという新婚のカップルの両親からレシピ本を聞き出し製本するサービスの開発に携わっていました。

CLでは珍しい社内やグループのサービスと携わっていました。CLはクライアントの広告をお手伝いする系の案件が多い中、今思うと自分はあまりそういった案件には関わらなかったですね。

pla-cole.wedding

prtimes.jp

去年の11月末ぐらいに異動しないかというお誘いがあり、SG(ソーシャルゲーム)事業部に異動しました。

ゲームチーム時代

数年運用しているゲームに配属されました。

中規模のチーム開発や、運用が長いタイトルの大変さを学びました。

10ヶ月ぐらいそのチームで過ごして、そろそろ他のことをしようかな〜と思ってた頃にブロックチェーンと出会いました。

ブロックチェーンと出会ってから

BuildersconでEthreumを知り、DAppsというものを知りました。

現在サーバサイドのエンジニアをやっていますが、自分の仕事がなくなる、もしくは置き換わって行くな〜という気がして、すぐさま勉強しようと思いました。

ビルコンの2週間後にBitcoinハッカソンがあるというをその前日ぐらいに気づいて、参加を決意しました。

そのハッカソンで優勝し、もっと実績を作りたいと思い、どんどんブロックチェーンハッカソンに参加します。

2ヶ月強でこれだけのハッカソンに参加しました。さまざまな人と出会い、いろいろなアイデアや、様々なチェーンの開発をしました。

これから

株式会社を登記するだけの費用を稼ぐことが出来たら、起業しようと思ってます。

とりあえず今はブロックチェーンに関係ない便利なアプリケーションの開発をしていますが、それが一段落すればブロックチェーンを使ったちゃんとユーザにメリットがあるサービスを作りたいですね。

ほしい物リスト

退職エントリーを書くときにはほしい物リストのリンクを貼るのが通例のようなのではらせていただきます。

全て書籍です。(Kindle本は不可能らしいので、ギフト券にしました。応援してくださる方はwasab1gjがIDのGmailドメインに送ってください)

http://amzn.asia/0ESqtgN

さいごに

会社を辞めたということをいうとよく聞かれるのが、「なぜやめたか?」なのですが、それは明確な答えが1つあります。

自分が成功しなかったことを会社のせいにしたくないからです。 100%の自由度で挑戦して、全て自己責任にしたかったのです。

会社のせいにするのってめっちゃダサいので・・・。

そんなこんなでこれから頑張っていきます。これからもよろしくお願いいたします。

GoのWebアプリケーションのSQLロガーとパフォーマンス分析ツールを作ってみた

はじめに

この記事は Go2 Advent Calendar 2018の記事です。

担当の10日より1時間ほど投稿が遅れてしまったことをお詫びします。

今回は、拙作のGo製のWebアプリケーションにSQLのクエリロガーを仕込めるOSS『graqt』 を紹介したいと思います。

過去に書いた記事を足して2で割ったような記事なので、graqtを知ってる方は読まなくてもいいと思います。*1

graqtとは

github.com

go-request-and-query-loggerです。

発音はガラクタでお願いします。

Go用のアクセスログとクエリのログを記録するためのhttp handlerミドルウェアで、そのログを解析するためのツールもついてます。

使い方

シンプルなWebアプリケーションの実装例

package main

import (
    "database/sql"
    "fmt"
    "math/rand"
    "net/http"
    "os"
    "time"

    _ "github.com/go-sql-driver/mysql"

    // 2つパッケージを追加してください
    "github.com/justinas/alice"
    "github.com/serinuntius/graqt"
)

var (
    // 2つ変数を追加してください
    traceEnabled = os.Getenv("GRAQT_TRACE")
    driverName   = "mysql"

    db *sql.DB
)

func init() {
    rand.Seed(time.Now().UnixNano())
}
func main() {

    // ifを追加して 環境変数 GRAQT_TRACEが1のときだけログを吐きます。
    // ISUCONで本番ベンチのときは オフにしておいたほうがいいと思います。
    // しかし、パフォーマンスには結構気を使ったので、そんなに低下はしないと思います。
    if traceEnabled == "1" {
        // driverNameは絶対にこれでお願いします。
        driverName = "mysql-tracer"

        // もし、ログを吐く場所を変えたかったら以下のメソッドを呼んで下さい。
        // デフォルトは、 query.logとrequest.logが このGoファイル(main.go)と同じディレクトリに出ると思います。
        // graqt.SetQueryLogger("log/query.log")
        // graqt.SetRequestLogger("log/request.log")
    }

    var err error

    // ここを先程の変数に変更してください
    db, err = sql.Open(driverName, "root:@/graqt")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    mux := http.NewServeMux()
    mux.HandleFunc("/user", createUser)

    // ミドルウェアを追加します。
    var chain alice.Chain
    if traceEnabled == "1" {
        chain = alice.New(graqt.RequestId)
    } else {
        chain = alice.New()
    }

    // ミドルウェアを渡します
    http.ListenAndServe(":8080", chain.Then(mux))
}

func createUser(w http.ResponseWriter, r *http.Request) {
    // contextを使用する方のメソッドに変えてください。
    ctx := r.Context()
    stmt, _ := db.PrepareContext(ctx, "INSERT INTO `user` (email,age) VALUES (?, ?)")
    t1 := time.Now().UnixNano()
    age := rand.Intn(80)

    stmt.ExecContext(ctx, fmt.Sprintf("hoge%d@hoge.com", t1), age)

    w.Write([]byte(`ok`))
}

CLI部分

gocuiというパッケージを使って実装しています。

alpをリスペクトして作ったCLIの動作例*2

今はこれより進化してて、普通に全部の情報が正しく表示されるはずです。

まとめ

ある程度便利なの作ったはずなので、煮るなり焼くなりしてあげてください。

*1:あまり居ないと思いますが

*2:すみません、再度録画するのが面倒で使いまわしです・・・

#isucon 8 で 惨敗してきました

以下のレポートは書いてる途中で力尽きてしまい不完全なものです・・・。あらかじめご了承ください。

2018/09/15(土)にISUCON8に参加して、ボロ負けしてきたのでそのレポートです。

SlackでのやりとりやGitHubのコードをベースに、時間ごとに何をやったかをざっくり書いていきたいと思います。

メンバー

@serinuntius アプリケーション・エンジニア

@dozen (会社の同期) インフラ・エンジニア

@kazasiki (会社の先輩) アプリケーション・エンジニア

面倒なので、このレポートでは敬称略とさせていただきます。

10:00~10:30

ポータルログインして、レギュレーションを読む。同時にSSHのコンフィグを書いてました。

次のようなことを確認しました。

  • ベンチマーが側の帯域がかなりでかい!
  • PHPは動かすのにH2Oのコンフィグ書き換えなきゃいけないのか〜大変そうだな〜(まだ、このときはGo実装でもH2Oが動いてるとはしらず・・・。ちゃんと「PHP以外の実装」という言葉がでてきているのにね
  • GET /initialize は10秒以内に返す
  • なお、問題ページの内容はできるだけ最新の状態を反映させる必要がありますが、1秒以内の遅延は許されます。 非同期処理をおすすめするような文言
  • アプリケーションは全て、保存データを永続化する必要があります。 これも気になった。

あと、ポータルを確認すると、ベンチマーカのIPアドレスが1つしか設定できないので、ロードバランスしないといけないんだろうな〜という話はしていました。

僕が alp, pt-query-diegest,dstat,notify_slack を事前に用意してた温かみのあるコマンドの羅列を直接打って入れましたw

僕がGoの初期実装に変更して、ベンチ回すと1194でした。

f:id:serinuntius:20180917083831p:plain

topとかで負荷を確認したところ、すごくMySQLが支配的で、ループとかIndexやばそうだねっていう話をしました。

@kazasiki が、Redisを全台に入れてくれました。

10:30~

@kazasiki のMacがフリーズする

@dozen が ホスト名をつけてくれて、今何でログインしているかとか、通信もisu2とかでいけるようになる。

@dozenがスペックを貼ってくれる

スペック
CPU: 2コア
MEM: 1GB / swap 2GB

@serinuntius が alpの設定をNginxに入れようと思ったら、H2Oで動いていることに気がつく。

H2O全然わからんし、剥がしてNginx(open resty)で行こう!ってなった。

けど、この判断は結論からいうとミスだったかもしれない。 そもそも、H2Oはボトルネックではないし、ちゃんと動いてたし、なんならNginxよりパフォーマンスがいいという話を聞く。 alp形式のログにするのも、ちょっと調べてみるべきだった。

alpでボトルネックを調査して、初動を決めるというムーブが完全にできなくなってしまったので、レコード数の調査やアプリを読むことにした。 @dozenに、Nginx移行を進めてもらいながら、@serinuntius, @kazasikiはアプリを読むという割り振りになった。

MySQLのレコード数やIndexのサイズをパッと出してくれるSQLを、@serinuntiusが出す。

MariaDB [torb]> select
    ->     table_name, engine, table_rows as tbl_rows, avg_row_length as rlen,
    ->     floor((data_length+index_length)/1024/1024) as allMB, #総容量
    ->     floor((data_length)/1024/1024) as dMB, #データ容量
    ->     floor((index_length)/1024/1024) as iMB #インデックス容量
    ->     from information_schema.tables
    ->     where table_schema=database()
    ->     order by (data_length+index_length) desc;
+----------------+--------+----------+------+-------+------+------+
| table_name     | engine | tbl_rows | rlen | allMB | dMB  | iMB  |
+----------------+--------+----------+------+-------+------+------+
| reservations   | InnoDB |   192507 |   57 |    15 |   10 |    4 |
| users          | InnoDB |     5173 |  307 |     1 |    1 |    0 |
| sheets         | InnoDB |     1229 |   53 |     0 |    0 |    0 |
| administrators | InnoDB |      101 |  162 |     0 |    0 |    0 |
| events         | InnoDB |       21 |  780 |     0 |    0 |    0 |
+----------------+--------+----------+------+-------+------+------+
5 rows in set (0.01 sec)

11:00 ~ アプリを読んでみる

@kazasikiとわいわいしながら、「何これ糞すぎ」「なにこれヤバイ」といいながらアプリを読んでいると、いくつかこれは確実にボトルネックになるであろうヤバポイントがあった。

getEvent, getEventsの「N+1」

こんだけの箇所で、getEventが呼ばれていた↓。

f:id:serinuntius:20180917090542p:plain

これを直すだけでも、だいぶ良さそう。

SHA2 の計算をわざわざMySQLに問い合わせて計算してもらっている

それ自体はそんなに重くないかもだけど、回数が多くなるとレイテンシが乗るしかなり無駄。

@kazasiki がざっと読んで、こんなメモを残していた。(原文ママ

* [ループクエリ]func getEventsからfunc getEventを読んでいて、getEvent内でsheetsやreserveを引いてる

* 455行目totalPriceが毎回reserveをgroupbyしてsum()してるからまずい

* 385行目/509行目でpasswordのsha2変換をmysqlにやらせている

* 517行目でuserをもう一回引いてるけど無駄?

* 533行のsanitizeEventはオブジェクトコピーがいらない気がする

• 167行目のgetLoginUserが毎回ユーザ情報をdbから引いている

11:30〜

@dozenのNginxへの移行が終わり、やっとalpでの集計ができた。 ちなみにNginxへ移行してスコアが200点ほど落ちた。

こんな感じのコマンドを叩いてました。

[root@118-27-24-38 nginx]# cat access.log |alp --aggregates="/api/users/.*, /api/events/.*/sheets/.*/.*/reservation, /admin/api/events/.*/actions/edit"|less

f:id:serinuntius:20180917091955p:plain

/admin/api/users/:id/ が遅い。

予想通り、 getEvent & getEvents がネックぽいってなったので、@serinuntiusはそれを修正することにした。 @kazasikiはtotalPriceのSUMをどうにかすることになった。

12:30〜

N+1も酷いんだけど、SheetってUPDATEも走らないし、INSERTもDELETEも走らないので、cacheすれば良くない?と思って、 /initialize でGoのメモリにキャッシュすることにした。 *1

ただ、cacheするだけじゃだめで、ハマった。何回かFailしてやっと意味がわかった。 ポインタ型を渡してしまうと、いろいろな処理でゼロ埋めみたいな処理とかが走って駄目だったみたい。

ポインタじゃなくて、実態を渡すことで回避した。 @kazasikiにいろいろと助けてもらった。

ただ、これはスコアにほぼ影響せず、12:38に score: 963 でマージした。

初めてのアプリの修正が競技開始2時間半って、

「うわっ・・・俺らのチーム遅すぎ・・・?」

ほぼ同時刻ぐらいに、@kazasikiの修正も終わったので、ブランチ変えて試してたら、Index貼るスキーマ, がなかったりして、修正してマージした。

12:54 score:1125

この2つの修正後のalp。まだgetEvent修正してないので、そんなに変化はない。 f:id:serinuntius:20180917122831p:plain

13:00〜

@serinuntius が getEventの修正をする。

@dozenがSlowログ出す。

@kazasiki は レポート作成時の不要なソートをしないっていうのをやっていた。 report作成時にやってる不要なソートを消す by kazasiki · Pull Request #6 · dozen/isucon8 · GitHub

15:00~

書くのも辛いが、getEventの修正にめちゃくちゃ時間がかかってしまった。

マージできたのは15時になった。 全部の関数には適当しなかったので、スコアは 1608

いろいろ自分がポンコツ過ぎて、@kazasikiにめっちゃ助けてもらった。 今思うと、常にペアプロで正確にガシガシ修正して行ったほうがよかったかもしれないと思った。

15:40~

別の箇所でもgetEventsを適用した。

score: 2289

17:00~

@kazasikiが直近の予約の取得を僕が作った、N+1改善後の関数にしてくれて、ここで大きくはねた。

スコアは 12635

なんかめっちゃ時間かかってしまったな〜という感じ。

*1:Goのメモリっていう表現はなんか変だけど、Redisではないっていうこと。