KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

KLab Advent Calendar 8日目の記事です。 こんにちは knsh14 です。

KLabではUnityを使って iOS、Android の端末で遊べるようなゲームを開発し、提供しています。 その一方で、それら以外の環境でも遊べるゲームが作れないかということで常に技術検証を行っています。 最近では 「ユニティちゃんのホームランスタジアム」 を Apple TV で提供したりしています。(紹介記事)

その活動の一環として、今回はブラウザで快適に動作する 3D のゲームを作るべく、Unity ではないゲームエンジンを使って開発したのでその紹介をしようと思います。

なぜUnityを使わなかったのか?

ブラウザ動作するためのゲームを開発するにあたって、幾つかの制約がありました。

  1. モバイル、PC のブラウザで動作すること
  2. 容量が小さい(理想は 3MB 以下におさまる)こと

僕らが普段使い慣れている Unity でも WebGL ビルドができ、PC やモバイルのブラウザで遊ぶことができます。ですが、試しに「ユニティちゃんのホームランスタジアム」をビルドしてみたところ、20MB 以上の容量になってしまいました。 これではとてもリリースできないので、Unity を使う選択肢を諦め、別のエンジンを使う道を探し始めました。

色々調べた結果、PlayCanvas というゲームエンジンが海外での利用実績などもあり良さそうだという結論になったので実際に試してみました。

PlayCanvasってなに?

  • 公式サイト
  • WebGL で動作することを主な目的としたゲームエンジンです
    • iOS で動作するように吐き出されるオプションもあります
  • その為吐き出されたゲームのサイズが最小で 10KB 程度と非常に軽いのが特徴です
  • 使用する言語は Javascript です
  • ゲームエンジンが Github で公開されているので、Unity と異なり動作をかなり細かいところまで追いかけることができます
  • エディタ上でチャットすることができ、お互いがその場にいなくてもペアプロのようにして開発することができます。

実際の開発画面

オブジェクトなどをいじるエディタ画面はこのような感じです。

シーンエディタ

だいぶUnityに近い感じで操作できます。

コードエディタ

Ace Editor を利用した感じのエディタになっているのでシンタックスハイライトもありますし、あまり使いにくいことはなさそうです。 このスクリーンショットだとわかりにくいですが、多少のコード補完もしてくれます。

実際にゲームを作ってみる

公式チュートリアル からタップでリフティングして落とさないようにするゲームを作ってみると一通りの使い方などがわかると思います。

PlayCanvas のメリット

開発環境のセットアップが要らない

ブラウザさえあればどこでも開発できるので便利です。

エディタ上で複数バージョンのプロダクトを管理できる

プロダクトを公開するときは、エディタ上から簡単に世界に公開することができます。 また複数バージョンをスタンバイさせておくことができるので、バグが有った場合にも簡単に前のものに差し戻すことができます。

Chrome でデバッグができる

Google Chromeは開発者ツールを使うことでかなり高機能なデバッグができます。 ブレークポイントをはって処理を止めながらデバッグすることができるのはもちろんですし、プロファイラで時間がかかっている処理を調べながら開発することができるので、初期からパフォーマンスを意識しながら開発することができます。

苦労話

PlayCanvas 上でバージョン管理できなかった

PlayCanvas からパブリッシュした成果物は PlayCanvas 上で一覧でき、昔のものを復活させることができます。 ですが、プロジェクトの状態は復活してくれないので、リファクタする時にかなり覚悟が必要でした。 Githubなどとコードを連携する方法 もありますが、Legacy なシステムなのでいつなくなるかわからないのが現状です。 Git でコミットするように成果物を Download しておいて Github で管理するか、Google Drive などに置いておくのが鉄則になりそうです。 どっちの方法でもシーンの状態を戻すことができないのでいい方法を模索中です。

なぜかありえない場所で衝突イベントが走る

物体を動かすために毎フレーム pc.Entity.setLocalPosition() で物体を動かしていました。 ですが、ある日物体が何もない場所でいきなり衝突イベントが走るバグに悩まされていました。 物理演算に任せて動いている場合は何事もないのですが、setLocalPosition() で物を移動させた後おかしくなることがわかりました。

いろいろ試した結果、pc.Entity.setLocalPosition() では Entitypc.Model は移動しますが、 rigidbody の位置が移動しないことが原因でした。

rigidbody がついた Entity を動かすときは

Sample.prototype.move = function(x, y, z) {
  this.entity.setLocalPosition(x, y, z);
  this.entity.rigidbody.teleport(x, y, z);
};

というメソッドを生やしてこれを使うほうが良さそうです。

まとめ

今回は Unity ではないゲームエンジンで WebGL ゲームを開発してみました。 PlayCanvas はどちらかと言うと個人でささっとプロトタイプを作ってすぐに見せるような場合に非常に便利なのではないかと思いました。 大人数で高品質なゲームを作るには Unity 少人数でとにかくスピード感を重視するような開発では PlayCanvas というように状況に応じて使い分けていけたらと思います。

(本稿はKLab Advent Calendar 2016 の4日目の記事になります)

Travis CIはよく知られたCIサービスの一つです。読者の方々の中にも、個人的なプロジェクトのCIに利用している人は多いのではないでしょうか。一方で、設定ファイル .travis.yml 中に秘密情報を暗号化して記述できることはあまり知られていないかもしれません。

YAML中での暗号化のやり方はTravis CIのドキュメント「Encryption keys」にも書いてあるのですが、 travis encrypt コマンドによりAPIトークンなどの秘密情報を暗号化して .travis.yml 中に記述するような仕組みになっています。この情報はTravis CI側で復号されてCIプロセス中で利用することができます。

今回指摘する内容は、この暗号の強度が多くのプロジェクトにおいて不足しているのではないかという点です。というのも、2015年4月以前に作られたTravis CIプロジェクトではRSA 1024bit鍵による暗号化が利用されているのです。

本稿ではTravis CIの暗号化の仕組みを簡単に説明した上で、どういうときに危険性があるかの詳細と鍵ペア再生成の方法を紹介します。

travisコマンドの概要

まずは travis コマンドを用いた暗号化について簡単に紹介します。 travis コマンドというのは gem install travis でインストールされるRuby製のコマンドで、内部的にTravis CIのAPIを叩いて色々とよしなにやってくれる便利コマンドです。

そのサブコマンドの一つが travis encrypt で、これを使えば任意の文字列を暗号化して .travis.yml 中に記述することができます。たとえば、Travis CI連携しているGitHubプロジェクト直下のディレクトリで下記コマンドを実行してみましょう。

$ travis encrypt 'FOO=secret_information'
Please add the following to your .travis.yml file:

  secure: "BEC97APcjoBsKRRGS4DCcQoLCviHTzK88JxfEq0wDfJ4+kfuLktyXEbHbG6Ct9cP+KLnwxIDBamf0pgOS7iQGLLb5Irn00fn4JEBeHd6kyTXQbyuPSe/NffVceg5vq8RWPT8nlWzVHD3wtjJFWz/Ocm6q5RkqvOtLszwM1Nc0Ig="

上記コマンドで出力された行を .travis.yml 中に書けば FOO=secret_information と書いたのと同じ意味になります。この仕組みにより、APIトークンやメールアドレスなどをYAML中に記述して外部サービスとの連携に使うことができます。

travisコマンドによる暗号化の中身

さて、この暗号化はどのような仕組みなのでしょうか? travis コマンドの中身を見ると、その正体がRSAを利用した公開鍵暗号であるとわかります。具体的には、travis encrypt コマンドが公開鍵で暗号化を行い、暗号文を受け取ったTravis CIが秘密鍵で復号するような仕組みになっています。

この暗号化に用いられるRSA鍵ペアの生成はTravis CI内部で暗黙に行われており、秘密鍵はTravis CIだけが持っています。つまり、公開鍵で暗号化した文字列は暗号化した本人でさえ復号できず、Travis CIだけが復号できるというわけです。

言い換えると、この暗号化を信用するためのポイントは2点だと言えます。

  1. Travis CIが秘密鍵や復号済みデータを流出させるようなことが無い
  2. 公開鍵への攻撃により秘密鍵を求められるようなことが無い

これさえ守られていれば、暗号化された情報は安全だと言えるでしょう。逆に上記のうちどちらかの問題が発生するようだと、秘密情報を .travis.yml に平文で書いたのと同じということになりかねません。本当に大丈夫なのでしょうか?

実は2012年に少数のプロジェクトで1の問題が発生したことがあるようですが、今後は安全だと仮定するしかないでしょう。今回は2の問題について議論していきます。

公開鍵への攻撃の可能性

今回指摘したいのは、RSA公開鍵への攻撃の可能性についてです。

まずはTravis CIの公開鍵にアクセスしてみましょう。リポジトリに紐付く公開鍵は下記のようにAPI経由で取得できます(参照:「Travis CI - API Reference - Repository Keys」)。URL中の hnw/php-build というのがユーザー名/リポジトリ名になっています。

$ curl -s https://api.travis-ci.org/repos/hnw/php-build/key | jq -r '.key'
-----BEGIN RSA PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOQb++oR7aBL6TfjSZbo/ssNrE
sV9FJmOn5TZktfAgLFv7T5c93Iot1k6ha7OO0FaZyf67bR+5Nou4Vd4SaiFpvb38
NMj4Pz9Smdwi3pWisqcgZaQOOpe9IB0nTAGhzZp8+2EPC1syRUi30FXOD03xnL0q
X8rhgIkuD6415tGP3QIDAQAB
-----END RSA PUBLIC KEY-----
$

ところで、この公開鍵は短そうに見えますよね。実はこれが1024bit鍵です。RSA 1024bit鍵の素因数分解は現代のスパコンでもかなり手強い計算ではありますが、コンピュータの性能向上により5年後なり10年後なりには現実的に攻撃可能になると考えられています。「暗号の2010年問題」の頃に2048bit以上の鍵への移行が叫ばれたことを考えても、1024bit鍵の寿命が近いのは間違いないと言えるでしょう。

ちなみに、鍵ペアが1024bitになっているのは一定以上古いプロジェクトだけです。最近のプロジェクトでは4096bit鍵が利用されていますので、今後プロジェクトを作る分にはこの問題はありません。正確な時期はわかりませんが、2015年4月頃に4096bit鍵に切り替わったように推測しています。

ご自分のプロジェクトの鍵ペアの鍵長を確認したい場合、上のようにAPIから公開鍵を確認すればわかります。公開鍵がASCIIで210文字くらいだったら1024bit鍵、730文字くらいだったら4096bit鍵ということになります。わざわざ鍵を確認しなくても、 travis encrypt の結果の長さも鍵長と同じサイズになるので、そこからも判断できます。

鍵の中身を正確に把握したい場合は openssl asn1parsedumpasn1 などを試してみてください。

鍵ペアを再生成する手順

もしかすると短い鍵ペアを使っているプロジェクトが見つかったかもしれませんね。もしそうだとしても、鍵ペアを再生成するAPIが提供されていますので安心してください。ちなみに、現時点ではAPIを直接叩く以外の方法は提供されていません。

鍵ペアの再生成APIを利用するにはAPIトークンが必要です。これは travis login コマンドでログインした後で travis token コマンドを実行することで取得できます。このAPIトークンを下記のように Authorization: ヘッダから送信すればAPIの認証が成功します。ただし、下記の hnw/php-timecop はユーザー名およびプロジェクト名です。

$ curl -d '' -H 'Authorization: token abcdefghijkl1234567890' -s https://api.travis-ci.org/repos/hnw/php-timecop/key

API呼び出しに成功すると新たな鍵ペアが生成されます。失敗した場合はおそらくトークンの指定が間違っています(ヘッダ中の token は消してはいけません)。元の鍵ペアが1024bitだった場合でも再生成すれば4096bit鍵になりますので、あと20年くらいは安心だといえそうです。

もちろん、鍵ペアを再生成した場合は改めて秘密情報の暗号化を行う必要があります。また、古い暗号文を遠い将来第三者が解読する可能性まで考えれば、暗号化対象のパスワード変更やAPIトークン再発行なども合わせて行うのが良いでしょう。

まとめ

  • Travis CIのtravis encryptによる暗号化にはRSAが使われている
  • そのRSA鍵の鍵長が1024bitのことがあるので要確認
  • 1024bitでは暗号強度の観点から中長期的に不安
  • 2015年4月頃までにTravis CI上に作られたプロジェクトが該当
  • API経由で鍵ペアの再生成ができる
  • 再生成すると4096bit鍵になる

本題とはズレますが、RSAを署名ではなく暗号化に使っている事例は比較的珍しい気がするので、その観点でも面白い話題といえるかもしれません。


@hnw

(本稿はKLab Advent Calendar 2016 の1日目の記事になります)

今年もアドベントカレンダーの季節がやって来ました。お祭り騒ぎと年末進行の勢いで、普段は書かないような変なネタも含めてKLabエンジニア陣が多彩なエントリーをお届けします。

最初は昨年に引き続きKLabアドベントカレンダーの旗振り役をさせていただいてますoho-sからです。 よろしくお願いします。

はじめに 「進歩した科学は魔法と見分けがつかない」

皆さんは不老不死に興味はありますか? 古今東西、人々は伝説に宗教にそして現実にそれを追い求めて来ました。 でも、そもそも「生きている」ってどういうことなのでしょう。 現代の生物学においても、その定義は非常に曖昧です。 特に急進的な主張としては、一個体の生物を構成する物理化学生物的な現象を全て完全に正確に模倣したら、それは生きているということではないか、というものです。 そして、その主張にのっとり、近年のSFで科学的に現実的? な方法としてよく見られるのが、脳のコンピュータへのアップロードです。

これは、人間の脳を構成するあらゆる情報をデータ化したうえで、ものすごい性能のコンピュータにシミュレーションさせることで、人間がコンピュータ上で生きていけるのではないか、という考え方です。 そうすれば、コンピュータ上のあらゆるデータと同じように、バックアップもとれるし、コピーもできるし、電源がある限り動き続けられるし、これ、不老不死じゃん! ということです。

もちろん、人間の神経細胞は140億個以上あるといわれ、それら同士の結合はさらに大変な数になります。当然現在の最高性能のコンピュータをもってしても、シミュレーションは不可能です。 さらに、神経細胞同士の接続の性質も完全にわかっているとはいいがたい状況です。

では、一方で、「その人」を維持するに足る正確さで各神経細胞や、その相互の接続の情報を取得することができるのでしょうか?

生物学の世界では、生物の全遺伝情報、いわゆるゲノムがデータ化できた現在、次のターゲットは、脳の神経細胞とその全結合情報のデータ化となっています。 電子顕微鏡やその他の観測手段を用いてデータ化していくのです。 そして、一個体の全神経細胞とその全接続の情報のことを「コネクトーム」と呼び、一部の生物のコネクトームはインターネットで公開さえされているのです。

つまり、自分自身のコネクトームを作成し、それをシミュレーションできたら不老不死になれそうです。 そう、SFの登場人物たちはきっとこうやって、いとも簡単に脳をコンピュータにアップロードし、コピーし転送しバックアップしアップグレードし、そして進化しているのでしょう。

しかし、ちょっと待ってください。エンジニアなら「それどんなミドルウェア使ってるの?」って気になりませんか? RDBMSのバックアップさえ一仕事なのに、一貫性や同一性やその他諸々は本当に大丈夫なのか。むしろどんなデータ構造なのか。 気になるところはいくらでも出て来ます。 そこで、GraphDBです。

コネクトームとGraphDB 「電気線虫はアンドロイドの夢を見るか?」

まずはコネクトームとはどのようなデータになるのかを具体的にお話ししましょう。 現在、公開されているコネクトームとしては、線虫( C. Elegans )のコネクトームがあります。 線虫は体長1mmほど、全細胞数およそ1000個、神経細胞は300個くらいです。 コネクトームの実態は、神経細胞をノード、それらの接続をエッジとしたグラフデータです。エッジの属性として、電気的・化学的結合の強さが格納されます。 つまり、このようなデータを永続化したり神経活動をシミュレーションしたりなどの様々な処理をするには、グラフデータを扱うのに適したデータ管理システム、そう、GraphDBがあればいいということになります。

GraphDBとは、その名の通りグラフデータ構造を格納して処理するためのDBMSです。 有名なものとしては、Neo4jなどがあります。 ノードとエッジからなるネットワークのようなデータ構造を、テーブルからなるデータ構造を管理するRDBMSのように、管理するものです。 RDBMSでグラフ構造を扱ったことのある人は、その煩雑さと低性能に悩まされたことがあるかもしれません。RDBMSでグラフ構造を扱うのは非常に難しいと言われています。GraphDBは最初からグラフデータ構造を扱うために設計されているために、はるかに速く安全にグラフ構造を管理できるのです。

GraphDBや、グラフデータを扱う仕組みは、例えばSNSのユーザー間の関係性であったり、検索対象ドキュメントの間の関係性であったりと、様々な対象が現実にあり、近年利用が進んでいる分野です。

ということで、本稿では、Go言語でグラフデータを格納し永続化できる簡単な仕組みを実際に作成し、そこに線虫のコネクトームを格納して、その線虫を不老不死にしてみましょう。

GraphDBを作る 「Connect! A to B」

まずはバックエンドのデータストアを決めます。もちろんここでグラフに向いた独自データストアを作り始めてもいいのですが、今回はLevelDBを使います。GoでLevelDBを使うに当たっては、公式ドキュメントと、こちらを参考にしました。 LevelDBはいわゆるKVSなので、グラフデータに適したキーを設計しなければいけません。今回は、以下のようなキーの構造にしました。

ノード

Name Size Description
NodePrefix=0x01 1byte プリフィックス
type 2byte タイプ
ID 4byte ID

エッジ

Name Size Description
EdgePrefix=0x02 1byte プリフィックス
FromID 4byte 始点ノードのID
Direction 1Byte エッジの方向
Type 2Byte タイプ
ToID 4byte 終点ノードのID

LevelDBはバイト列の前方一致による検索が可能なので、これで、

  • ノードの全検索
  • ノードのタイプによる検索
  • ノードのタイプとIDによる検索
  • エッジの全検索
  • エッジの始点IDでの検索
  • 始点IDと方向での検索
  • 始点IDと方向とタイプでの検索
  • 始点IDと方向とタイプと終点IDでの検索

ができます。 一方で、このキー構造だと、終点から始点への検索ができないので、エッジを格納するときは、始点から終点までの前向きエッジと終点から始点までの後ろ向きエッジを同時に格納することで解決しました。

こちらが、実際に作ってみたリポジトリです。

以下にノードとエッジのコードを提示します。

また、DBのOpenや検索等のためのコードが以下になります。

公開されている多くのGraphDBには、ロックやトランザクションや各種制約、クエリ言語やサーバーとして動かすための仕組みなどがありますが、今回はLevelDBにグラフ構造を永続化して、簡単なノードの検索とエッジの検索ができるようになったということで、ここまでにしておきます。

線虫のコネクトームを格納する 「発進」

線虫のコネクトームデータはこちらのものを利用しました。オリジナルのデータはdoi: 10.1126/science.1221762.を参照してください。dot形式のデータで取得してテキストエディタ等で置換とマクロでTSV形式の単純なデータに変換しました。

実際のデータはこのようなものです。

これを作成したGraphDBに読み込みます。

読み込みは以下のようなコードを書きました。

// InputFromTSV read two tsv files (node and edge) and insert node and edge into DB
func inputFromTSV(db *graphdb.GraphDB, nodefilename string, nodetype int16, edgefilename string, edgetype int16) {
    nodefile, err := os.Open(nodefilename)
    if err != nil {
        panic(err)
    }
    defer nodefile.Close()

    nodereader := csv.NewReader(nodefile)
    nodereader.Comma = '\t'
    for {
        record, err := nodereader.Read()
        if err == io.EOF {
            break
        } else if err != nil {
            panic(err)
        }

        node := &graphdb.Node{
            Nodetype: nodetype,
            ID:       no2ID(record[0]),
            Value:    []byte(record[1]),
        }
        db.AddNode(node)
    }

    // ==========================================================================

    edgefile, err := os.Open(edgefilename)
    if err != nil {
        panic(err)
    }
    defer edgefile.Close()

    edgereader := csv.NewReader(edgefile)
    edgereader.Comma = '\t'
    for {
        record, err := edgereader.Read()
        if err == io.EOF {
            break
        } else if err != nil {
            panic(err)
        }
        db.AddEdge(no2ID(record[0]), no2ID(record[1]), edgetype, []byte(record[2]+","+record[3]))
    }
}

func no2ID(no string) []byte {
    noint, _ := strconv.Atoi(no)
    id := make([]byte, 4)
    binary.LittleEndian.PutUint32(id, uint32(noint))
    return id
}

そして、まずは永続化した線虫のコネクトームをDOT形式で書き出し、データの確認をします。

func main() {
    db, _ := graphdb.Open("celegans.db")
    inputFromTSV(db, "c.elegans_neural.male_node.tsv", NEURON, "c.elegans_neural.male_edge.tsv", CONNECTION)

    db.PrintGraph2DOT()
}

以下が出力したdot形式ファイルをGraphVizで書き出したものです。

C. Elegans のコネクトーム

次に、ノードに接続するエッジを検索してみましょう。 コードはこんな感じになります。

func main() {
    db, _ := graphdb.Open("celegans.db")

    start := db.GetNode(NEURON, no2ID("0"))

    edges := db.GetNodesEdge(start)

    for _, edge := range edges {
        fmt.Println(graphdb.Byte2string(edge.To))
    }
}

さらに、こうすると、Hop数を変えてノードを羅列できます。

func main() {
    db, _ := graphdb.Open("celegans.db")

    start := db.GetNode(NEURON, no2ID("0"))
    fmt.Println(graphdb.Byte2string(start.ID))

    rec(db, start, 0, 4)
}

func rec(db *graphdb.GraphDB, node *graphdb.Node, curdepth int, maxdepth int) {
    curdepth++
    if curdepth == maxdepth {
        return
    }

    edges := db.GetNodesEdge(node)

    for _, edge := range edges {
        fmt.Println(strings.Repeat(" ", curdepth) + graphdb.Byte2string(edge.To))

        next := db.GetNode(NEURON, edge.To)

        rec(db, next, curdepth, maxdepth)
    }
}

いかがでしょう。これで僕のPC上で線虫が不老不死となりました。

・・・そんなわけない!

まとめ 「未来を予測する最善の方法」

今回はあくまでも実生物の神経ネットワークを使いやすい形で静的に永続化したにすぎず、生物のダイナミックな「生きている」という性質は、シミュレーションを動かしたりしなければ得られません。不老不死ははるか遠い。

今回、なぜこのような題材を選んだかということを最後に説明しないと意味不明になりそうなので、説明しておきます。

まず、GraphDBに興味があったこと、特に、今あるものを利用するのではなく、どう実現されているのかに興味があったので実際に作ってみようと考えました。 また既存のGraphDBはサーバーを立てて使うものが多いのですが、プログラム組み込みで使うものが欲しかったというのもあります。 と言っているところで、EliasDBというものを見つけました。 今回は、それRDBMSでもいいじゃんというレベルしか実装できませんでしたが、GraphDBはとても面白いので、今後も勉強していきたいと思います。

そして、GraphDBを使った題材を考えていて、コネクトームというものを知り、これだ!と思ったわけです。 コネクトームに関しては、ロボットに線虫のコネクトームを元にしたシミュレーションシステムを接続し、電子線虫サイボーグを作る研究など、とてつもなく未来な研究が実際に行われています。例えば、ここです。興味のある方は調べてみると面白いと思います。

それでは、引き続きこの後のKLabアドベントカレンダーをお楽しみください。

↑このページのトップヘ