コミックマーケット100(C100)振り返り

第100回目(!)のコミケでCoppersmith法についてのコピー本を頒布した。 これについて、誰向けかも分からないような話をつらつらと書いていく。

CTFのCryptoで使うCoppersmith法を解説する本

Coppersmith法とは?

数学とか暗号とかに興味の無い人は、ここは飛ばしてください。 興味のある人はここだけでも読んでください。

Coppersmith method - Wikipedia

F(x)=x^{n}+a_{n-1}x^{n-1}+\dots+a_1 x+a_0 について、F(x)\equiv 0 \mod M\left|x\right| \leq M^\frac{1}{n} を満たすような解を多項式時間で求めるアルゴリズムである。

一瞬、「解の制約からmodの意味が無くなるのでは?」と思うかもしれない。 しかし、x^{n}Mを超えないが、a_1 xなどは超える。

中ではLLL格子基底簡約を使っている。 LLL基底簡約の使い方の一種として、a_0 y_0 + a_1 y_1 +\dots + a_n y_n \equiv 0 \mod M を満たす小さな y_0,\ y_1,\ \dots\ y_n を求めるというものがある。 今度は「x^{0}=y_{0},\ x^{1}=y_{1},\ \dots,\ x^{n-1}=y_{n-1} と置いて、その使い方をするだけでは?」と思うかもしれない。 解の制約がもっと厳しいならこれで解けるが、Coppersmith法の制約は無理である。 雑に言うと、y_{0},\ y_{1},\ \dots,\ y_{n-1} のビット数が M のビット数を超えるので解がいくらでも出てくる。

で、どうするの? というのが本題。 上記のようなLLL基底簡約の良くある使い方をしていなくて面白い。

この話、終わった後に書いていないで、頒布物の紹介ツイートにでも書いておけば、頒布数が違ってきた気もするな。 後述のように時間が無かったのでしょうがない。

ちなみに、「Finding a Small Root of a Univariate Modular Equation」という論文に書かれている。 ガチ勢には、「で、その本の論文からの差分は何なの?」とか訊かれそうだな。 特に無いので、論文を読んでください。

参加サークル数

www.bigsight.jp

コミケの会場の東京ビッグサイトは、元々東展示棟6ホール+西展示棟4ホールだった。 このうち西展示棟の2ホールは企業向けで、サークル向けは残りの8ホール。 これで3日開催。

東京オリンピックのために東展示棟が8ホールになり、南展示棟4ホールも新設された。 一方、オリンピックのために使えない棟があったりもしたが、今はフルに使える。 で、開催期間は2日間。

単純計算でだいたい同じくらいのサークルが入りそうだが、入場券確認&検温後の待機スペースに割り当てたり、コスプレに割り当てたりで、結局サークル向けに割り当てられるホール数は以前と変わらず。 それで日数が少なくなった上に、感染症対策でサークル間の空間を広く取ったりで、サークル数は半分近くに減っている。

時系列

2月頃。前回のC99は来場者もそうとう絞っていて、本を買いに来てくれる人もほとんどおらず、「次は参加しなくて良いかな」と思っていた。 でも、第100回は出られるなら出たいなと申込み。 上記のように参加できるサークル数が少なく、100回記念で申し込むサークルも多いだろうし、当選することは期待していなかった。 期待していなかったので特に準備もしなかった。

6月頃。当落発表。受かっていた。まじか。 本を書こうと頑張り始める。 当初は別の本を書こうと思っていた。

7月頃。新型コロナ第7波。本は書けないし、感染者数の増加で「これはまた中止になるのでは……」という感もあり、やる気無し。 ちなみに、印刷所に製本を頼む場合、基本的には7月末~8月頭くらいが入稿の目安。 多少は金で解決できる。 数割値段が高い代わりに、締切りが数日伸ばせるという「特急料金」があったりする。 これが資本主義か。

いや、でも、もう無理では。 「中止になったら原稿を書く必要も無いんだよな……」で「コミケ 中止」でTwitter検索をしていた。 「お前、新型コロナ下で音楽フェスが開催されていること文句を付けているけど、その名前の『@C100~』は何だよ。コミケも中止しろ」などの揉め事が起こっていたりした。

7月末頃。 中止になると思っていたけど、運営のスタンスとしては「お上に中止にしろと言われない限りはやる」で、政府も緊急事態宣言を出すつもりは無さそう。 本を出さなかったからと言って誰かに怒られるわけではないけれど、せっかくの100回だし何とかしたいよなぁ。 テーマを絞ってページ数を減らしてのコピー本なら何とかなるのでは? で今に至る。

コピー本

コピー本とは、コンビニのコピー機などで印刷してステープラー(ホチキス)で綴じた本である。 少部数なら印刷所に頼むより安いし、前日に作業をしても間に合う。 代わりにクオリティは落ちる。

kinko'sかセブンイレブンなどのコンビニのコピー機を使えば良い。 どちらも「同人誌が作れるよ」とアピールしている。 どちらでも変わらなそうなものだが、できることできないことが微妙に違っていて悩ましい。

www.kinkos.co.jp

kinko's。プリンタにフィニッシャーが付いていてステープラー留めもしてくれる。さらに、表紙を別の紙にすることができる。たとえモノクロ印刷でも表紙が厚紙になっているだけで、だいぶ印象が違う。それならkinko'sで良いのでは? と思うが、小冊子印刷をする場合は、なぜかUSBメモリから印刷することができない。一旦印刷してコピーする必要がある。画質が落ちるだろ……。有料で店頭のPCを借りるとできるという噂も聞いたので、今度試してみたい。

昔は24時間営業だったが、新型コロナの影響で24時間営業は取り止め。 土日祝日が休みの店も多いので注意。 今回のコミケは、前日は平日だが、前々日が祝日だった。

www.doujinshi-print.com

セブンイレブンUSBメモリから直接小冊子にできる。しかし、ステープラー留めをしてくれない。 USBメモリで持ち込むのではなく、インターネット経由でデータを送ることもできるが、1枚(両面の場合は1面)10円のところが、20円と倍になる。 大量に印刷しようと思うとUSBメモリが必須。

kinko'sで表紙を厚紙に印刷して、セブンイレブンで本文を印刷するという合わせ技にすることにした。

www.doujinshi-print.com

セブンイレブン(kinko'sも?)のプリンターは用紙の縁ギリギリまで印刷することができない。 それでは端のほうが切れてしまって困るので、デフォルトでは「ちょっと小さめ」に印刷するようになっている。 本文は文章で余白があるからそんな配慮は要らないんだけど……ということで、「原寸印刷」ができるようになっている。 が、しかし、小冊子印刷の場合はなぜか原寸印刷ができない。 なぜ? 上記のページだと縮小されることを見越してちょっと大きめのサイズで作れと言っている。 Re:VIEWで作るPDFでもめっちゃ頑張ればできるのかもしれないが、やりたくないぞ……。

B5の元データを小冊子のように面付けしたB4両面のデータを作り、小冊子印刷ではなく普通に両面印刷するという手を思いついた。 そのデータをどうやって作るのかというと、手元のPCでPDFを開いて小冊子印刷の設定で印刷してPDFにすれば良い。 今でこそ各ソフトからPDFで直接保存ができるけれど、元々PDFといえば印刷でプリンタとしてPDFを選ぶものだった(よね?)

手元のPCの印刷のところには「Adobe PDF」と「Microsoft Print to PDF」が出てきた。 普通に考えれば、本家本元の「Adobe PDF」のほうがクオリティが高そうなものだけど、なぜか黒が濃いグレーになり、画像の画質がとても落ちてNG。 もしかしたら設定で何とかなったのかもしれないが、分からん。 「Microsoft Print to PDF」だと色は問題無し。 画像の画質は、「Adobe PDF」よりは良いが、やっぱりJPEGにされてちょっと画質が落ちる。 画像については、IllustratorからPNGで書きだしていたものをEPSにしたら劣化しなくなった。

PDFをデータそのまま配置だけどうにかしてくれれば良さそうなもので、そういう有料ソフトもあったが、試してはいない。 誰か試したら感想を教えてほしい。

www.bookletcreator.com

中綴じ。 針の向きを変えられるホチキスが売っていて、とりあえずこれを使えば中綴じができる。

www.max-ltd.co.jp

とりあえずはこれでも何とかなるのだけど、紙がズレないようにするのがなかなか難しい。 本気を出すならこれ。

www.max-ltd.co.jp

東急ハンズで「高いなぁ」と思いながら定価(4,400円)で買ったけど、ヨドバシ.comだと半額くらいで買えるな。 まあ、こんな数が出なそうな商品を実店舗で安売りはできないか。 半分に折ってから綴じようとすると紙がずれがちなので、このホチキスのガイドをきっちりB5に合わせて、綴じてから折ったら良い感じだった。

もっと本気を出すならこれか。 針も普通のホチキスよりちょっとゴツいやつで良さそう。 誰か買ってみてほしい。

www.max-ltd.co.jp

針はステンレス製のものを使うと良い。 ちょっと高いが、元が安いので誤差。 ただ、上の「もっと本気を出す」やつ用の11号針はステンレス製が無い。

www.max-ltd.co.jp

最後に、中綴じ用のホチキスを使っても、どうしても多少は紙がずれるので、昔自炊用に買ったゴツい裁断機で端をちょっと切った(化粧断ちと言うらしい)。 これでだいぶ見た目が良くなる。 B5ではなくなるので、他のB5の本と揃わなくなるが……端がずれていたらどうせ揃わないので、まあ良いだろう。 どうせ端を落とすなら、セブンイレブンのプリンターの「ちょっと小さめ」のままで良かったかもしれない。

www.durodex.co.jp

なかなかのクオリティでは(自己満足)。

コミケ当日

前回のC99よりはだいぶ人が増えているけれど、新型コロナの前に比べるとまだまだ頒布数が少ない。 いや、私のは急遽作ったコピー本だし、ろくに宣伝もしていないが、周囲のサークルを見る感じ。 コミケに行ったことが無い人のイメージは、大行列で本が飛ぶように売れているというものかもしれないが、あれは一部の人気サークルだけであって、大半のサークルは元々そんなことはない。 でも、もうちょっと売れていたような気がするんだよな。

会社の周りの飲食店は当然人気の店と不人気の店がある。 新型コロナに伴うリモートワークで客が減り、客の合計が半分になるなら、てっきりどの店も客が半分くらいになると思っていた。 実際は、人気店はたいして変わらず、不人気店から一気に客が減って、次々と潰れていった印象がある。 コミケもそんな感じなのか、大手のサークルは新型コロナ前と客数があまり変わっていなかった気がする。

コミケ2日目

2日目は(サークル参加ではない)一般参加。 受付時間09:00~09:30(E枠)のチケットを買っていた。 遅れたらどうなるんだろうね?

【Q6】一般参加の受付時間内に間に合わなかった場合、どうすれば良いですか?

【A6】受付時間については、設定された時間以降であれば受付後入場可能です。ただし、アーリーチケットであっても、午前入場の開始準備が始まった後については、午前入場待機列の最後尾に並んでいただく場合がありますので、予めご了承ください。

https://www.comiket.co.jp/info-a/C100/C100EntryTicket2.html

こう書かれているけど、細かい扱いが分からない。 一番後ろに回されても困るし、ちゃんと時間通りに行こう……と思っていたが、初日の疲れで寝坊。 特にどうということもなく、その時点で待っていたG枠などの人達を横目にすっと入れた。 次も同様の形式かもしれないのでメモ。

ゆりかもめで行った。 「一般参加はあっち」と言われて歩いて行き、受け付け前の人達の横を通って、ぐるっと回って元の場所に戻ってきた。 りんかい線で来るのがデフォであって、りんかい線ならそんなことも無かったのかもしれない。 次は朝はりんかい線で行こう。 メモ。

RECRUIT 日本橋ハーフマラソン 2022夏(AtCoder Heuristic Contest 013)参加記

atcoder.jp

github.com

暫定201,809点、169位。システムテスト後は8,566,178点、165位。

Seed 0から7の結果。

コンテスト開始前

1位の方の回答がTシャツに!?

1位の方の回答がTシャツに!? これは欲しい。ということは、「映える」ようの結果が出てくる問題なのだろうな。どんなのなんだろう。

8月9日~8月14日

コミケが忙しかった。 問題は見ていたけど、特に考えてはいなかった。

考察

後から参加する人の特権として、順位表から得られる情報が考察に使える。 順位とスコアをグラフにプロットしてみる。

順位表をそのままExcelに貼り付けたらセルが結合されていて面倒そうだったので、JSONを保存してPythonでゴニョゴニョ。 AtCoderのスコアって、内部的には実際のスコアを100倍した整数なのか。

25万点のところでグラフが折れ曲がっている。 25万点というスコアは、1種類のコンピューターを全てクラスタにしたときに得られるスコア。 そこから2種類目のクラスタを大きくできるかどうかに壁がある? 5000点でも曲がっている。 これは……なんだろうね。

クラスタに他の種類のコンピューターを混ぜるべきかどうかで、やることがだいぶ変わりそう。 スコアを考えてみると、50台のコンピュータのクラスタ2台よりは、間に他の種類のコンピュータを混ぜてでも繋げたほうが得だな。

ヒューリスティックコンテストと言えば、手法は焼き鈍しかビームサーチ。 移動にちょっとビームサーチっぽさを感じるけど、そんなに動かせないし、焼き鈍し一択だろう (貪欲的な解法は完全に発想から抜け落ちていた。結果的にそっちのほうが正解だったっぽい)。

接続と移動の合計がコンピューター数。 ということは、クラスタの個数だけコンピュータを移動することができる。 この問題設定の意味は……? とか考えていた。

焼き鈍し

まずは、移動は無しで、接続を切ったり繋げたりで焼き鈍し。 本当は、クラスタの構成を変えずに繋げ直す(一度ループを作って別の場所を切る)というのができると良いのだろうな。 切って繋げては一度スコアの谷を下りることになるので、遷移がしづらい。 でも、時間が無いからそんなのの実装は無理。

10台のクラスタを11台にするのと、90台のクラスタを91台にするのとでは、スコアの上がり方が全然違う。 これを同じ温度で扱うのは無理があるだろう。 どうするかな……。 スコアは台数の2乗に比例するので、スコアの平方根を使えば良いか。 時間が無いので、ちゃんと検証はしていない。 適当。

91,223点

上位陣が30万点であることを思うと、まずまずのスコア。 方針は合っていそうだな(合っていなかった)。

線を繋ぐときに交差する線を切る

横の赤線が配線済みで、縦の青線を配線しようとするとき、今まで単に諦めていた。 焼き鈍しの過程で、赤線が切れた後に、青線が繋がることを期待するしかない。 それは無茶なので、線が交差していたら、諦めるのではなくその線を切るようにしてみた。

99,082点

サーバーの移動

そろそろ移動を考えるか。

移動の回数について。

これは、ローカルテスターに入っていた100個のテストケースから今の実装で得られた結果について、Kの値でグラフを分け、Nの値を横軸に、配線が終わった後の残り手数を縦軸に取っている。 サーバーを移動したことによって配線しやすくなることを考慮して、このグラフのちょっと下あたりの回数を移動に割り当てれば良いだろう。 本当は移動回数を色々と変えて、どのくらいを割り当てるのが最適なのかを検証するべきだろうが、そんな時間は無い。

移動の方法について。 移動の順序まで考えないといけないのは面倒。 ビジュアライザの結果を見るに、1マス移動するだけでもだいぶマシになりそう。 で、さらに、他のサーバーの元の位置も現在の位置も移動先には選択しないとすれば、互いに干渉しなくなって楽になるはず。

移動の結果の評価について。 上下左右を見て、最初にぶつかるサーバーが同種なら+1で良いだろ。 時間が無いので適当。

最初に移動。その後で配線をするという作戦である。 これは良くなくて、配線をしながら「ここにサーバーがあったらなぁ」というのを考えながら移動をするべきだろうが……どうやったら良いのか分からん。

184,088点

だいぶ良い感じになってきた。

種類によって重み付け

上位陣のスコアを見るに、全ての種類のサーバーで大きなクラスタを作るのは無理である。 各種類がそこそこの大きさよりは、特定の種類だけ大きなクラスタになっているほうが良い。 移動の段階で種類1だけ、スコアを5倍にしてみる。

201,809点

20万点を超えたし、運が良ければ景品をもらえる順位である200位以内にも入った。 満足。 終わり。

AtCoder Heuristic Contest 011(AHC011)参加記

atcoder.jp

暫定35,184,653点(得点率70%)で89位。

追記:システムテストは2,098,827,035点(得点率70%)で89位のまま。

GitHubリポジトリ

github.com

解の一例(seed=4)

img.atcoder.jp

最初の土日

無。5月28日(土)~6月5日(日)のコンテストで土日が2回ある。

月曜日

問題を見始める。

スライディングパズルで木を作る。 問題の生成方法は、まずは全域木を作って、解くときの手数の上限回動かして崩すというものなので、全てが繋がった木が作れることは保証されている。 50%の点数が境目で、手数上限で全域木が作れると50% 手数がより少なければ点数が上がり、全域木が作れなければ、最大の木の大きさに応じて点数が下がっていく。

まだコードを書かずに、Twitterに上がるseed=0の完成図を眺めていた。 全域木が完成している人は、空白が右下の人が多い。 最初に正解盤面を作るときの空白は右下なので、右下の解があることは保証されている。 しかし、右下以外の全域木も上がっているので、右下が必須というわけでもない。 どういうことなのだろう。

そして、何をやってよいのか全く分からない。 「なるべく大きな木を作りましょう」なら色々とやることはあると思うが、上がる画像を見るに、ある程度の順位を取るには、全域木が前提っぽい。 普通に木を大きくしていくだけでは最後に詰まると思うんだよな。

火曜日

リポジトリを作って真面目に考え始める。 (ちゃんとスライドさせるのではなく)タイルを浮かしても良いから並び替えて目標の全域木を作る → スライドパズルとして解いて目標の全域木にする という方針が良さそうである。 スライドパズルにはパリティがあって、(タイルを浮かして並び替えた)全ての盤面のうちの半分は作れない。 しかし、この問題では最小サイズが35タイルなのにタイルは16種類で、必ず重複しているタイルがある。 鳩ノ巣🕊️ そのタイルを入れ替えればパリティを変えられるので、必ず解ける。 もちろん目標の全域木を決め打ってしまっては最適な解には辿り着けないけど、上述のように、普通にスライドさせて全域木を作るのはとても難しいだろう。

まずは見た目から入るとやる気が出る。 盤面の出力。 罫線素片(┗┏┃┻など)が使えそう……と思ったら、一方向にだけ出ているものが私の環境だと幅が違って合わない。 結局罫線は諦めて、こうした。

   +     +     +-- --+-- --+-- --+
   |     |           |           |
   |     |           |           |
   +-- --+-- --+-- --+     +-- --+
         |     |     |     |     |
         |     |     |     |     |
   +-- --+     +     +     +     +
               |     |     |
               |     |     |
   +     +-- --+     +     +-- --+
   |     |           |           |
   |     |           |           |
   +-- --+-- --+     +-- --+     +
               |     |     |
               |     |     |
   +-- --+-- --+     +     +     +

全域木を作る方法。 左側に出ているタイルの個数と右側に出ているタイルの個数は同数だよね? その辺を考えると、実は、閉路ができないように、場面外に辺が出ないようにと作っていけば、詰むことが無かったりしない? → そんなことはなく普通に詰む。 ですよね。 2個のタイルの交換を近傍として焼きなませばいけるだろう → あまり上手く行かない。 7割くらいは全域木ができるのだけど、失敗することも多い。

全域木を作るのは一旦置いておいて、目標となる盤面ができたときにスライドパズルとして解く方法を考えよう。 手で解けるのだからそれをプログラムにすれば良いのだけど、とても面倒だな。 左上から順に揃えていくことにして、左上から何個目まで揃っているかと、次に揃えるタイルが目標位置とどのくらい近いかをスコアにして、ビームサーチをすれば良い感じになってくれるのでは? なってくれませんでした。各行の残り2個のタイルと最後2列は、一度崩してから並び替える必要があり、適当に書いたビームサーチではそこを乗り越えてくれない。

どちらもダメで、今回はサボろうかなと考え始める。

水曜日

種類ごとのタイル数が揃っている&全域木ではない から 種類ごとのタイル数が揃っている&全域木である を作ろうとしていた。 これ、種類ごとのタイル数が揃っていない&全域木である からスタートしてはどうだろうか? という発想に至る。

初期盤面。タイルの交点を中心にしたときに4辺のうち3辺が埋まっている箇所が必ずある。 ここを回しても全域木であることは変わらず、タイルの種類が変わる。

これで焼きなましたら、だいたい全域木ができるようになった。 タイルの種類を変えていく場合、焼きなましに失敗したらどうしようもなくなるという問題はあるが……まあ何とかなるだろう。

スライディングパズルパート。 行ごとの最後2個のタイルについて、揃える過程に加点をして何とかした。

これで半分くらいは全域木が完成した50%のスコアを取れるようになった。 サブミットしてみたらTLE。 まあ、高速化の余地はまだいくらでもあるだろう。

木曜日

ビームサーチをする → 完成しなかったらパリティが違っていたと判断して、1箇所入れ替えてもう一度ビームサーチ としている。1回目で完成したかどうかで実行時間が倍違う。実際に解こうとしなくてもパリティは調べられるので、調べる。

途中で止まって全く進まなくなることがある。 状況を確認してみるとこうなっていた。

これ、先の図のポイントでは、4が1 ptの位置にいるので1 pt加算される。 ここから2 ptの状態に進むには4を崩さないといけない。 それができていなかった。 隣に5があったら4が目標位置にあってもポイントを加算しないようにした。

全域木がたまに作れないことがある問題については、単に全域木を作るパートを複数回実行するようにした。 ここは軽いので問題無い。

サブミットして27,529,855点。 全てのケースで全域木が作れるようになると、25,000,000点以上になるので、ドヤ顔でツイートしてみる。 なお、全てのケースで全域木が作れると25M点以上になることと、25M点以上なら全てのケースで全域木が作れていることは同値ではない。 実際このときも、他のケースで点を稼いでいるから25M点を超えているだけで、全域木が完成しないケースもちらほらあった。

そういえば、

こんなツイートをしたけれど、ビジュアライザの「使い方」に後から気が付いた。

rich モードにすると、ランダムノイズが入って同じ種類のタイルの区別が可能になります。

手間が掛かっているし、実用性もあった。 同一の形状なら同じ模様だと思っていた。 すみません。

金曜日

盤面を2次元配列で扱っていたのを1次元配列化。 高速化に寄与するし、意外とコード的にも1次元配列のほうが楽。 どうせ最後にはやるので最初からやりましょう。 単純な書き換えではあるものの、どこかしらはミスるので、デバッグが面倒。

全域木から同じ種類のタイルを対応付けてスライドパズルの問題を作る部分で、今までは残っているものを貪欲に取っていたところを、距離の合計が最小になるようにした。 いや、最小費用流とか全探索とかで厳密に最小にはできるだろうけど、面倒だったので適当に焼きなまし。 簡単な問題だし、だいたい最適解になるだろう。 たぶん。 これがけっこう効いた。 貪欲に取ってもだいたい最小になるんじゃないかと思ったけど、1次元で貪欲に取っているので、2次元での距離を考えれば、そりゃそうか。

今までは全ての行で左から右に揃えていたところを、左→右と右→左を交互にした。 これは有効だろうと思っていたのに、あまり変わらず。 まあ、悪くなることはないだろうから、いいか。

ビーム幅を盤面サイズによって変えた。 手数上限が盤面幅Nの3乗で、1手あたりの処理は盤面の面積に比例するから、ビーム幅が同一ならば実行時間はNの5乗に比例ということで良いだろうか。 Nによる影響大きいな。

まだ100ケース中の数ケースで途中で詰まるものがある。 見てみるとこの形だった。

この形の何がダメなのかパッと見では分からなかった。 最後2個のタイルは全く揃っていない状態なので、次は4を右上に置こうとする。 その時の手順を考えると、先の4と5が逆になった形に必ずなるので、揃った状態まで辿り着けない。 こういうパターンが他にもありそうで怖いが……100ケースでは無くなったから、とりあえずいいだろ。 問題が出てくる度に評価関数で1個1個対処するのではなく、ビームサーチ側で何とかしたくはある。

残り時間が少なくなったときにビーム幅を減らす緊急回避を入れる。 これは単にTLEを回避することだけが目的ではなく、緊急回避があることによってビビらずにビーム幅を増やせるというメリットがある。 今となっては手数が上限に達することはほぼ無くなったので、上限まで回しても間に合うようなビーム幅にするのは無駄。 緊急回避モードに入っても意外とスコアが落ちなかった。 最後のほうは残りマスが少なくて優劣が付かないからそりゃそうか。 結局、緊急回避と言いつつ、数割はこのモードに入るようにしている。 もっと考えを推し進めれば、ビーム幅がずっと一定 → 急に減らす ではなく、徐々に減らしていくという手もありそうだが……やってはいない。

最後の土日

SECCON Beginners CTFと、Google Code Jam Round 3と、Pokemon GO Festで忙しくて、気が付いたら終わっていた。

パリティが合わなかったときにランダムに2個のタイルを入れ替えているけれど、これはタイルが遠いところになってしまう可能性がある。 パリティが一致するようにしたまま焼きなますのは試したかった。

ゆっくりMovieMaker4のFFmpegエンコードで出力した動画が再生できないときの対処法

manjubox.net

YMM4は、エンコード時にMediaFoundationとFFmpegが選べる。 音声の動画全体に占める割合は微々たるものだし、高音質なほうが良いかなと思って、320 kbpsが選べるFFmpegエンコードで出力している。

たまに、エラーも無く出力したのに再生できないことがある。

その度にエンコードし直すと時間が掛かってしまう。 再生できない原因と修復方法を調べたのでメモしておく。

MP4は複数の「box」で構成されている。 この内、mdat boxのサイズがなぜか間違って出力されるのが原因。

修復方法。

バイナリエディタを用意する。 私はHxDを使っているけれど、何でも良い。 選択した範囲のサイズを16進数で確認できる機能があると楽。

mh-nexus.de

「moov」という文字列で検索し、その4バイト前にカーソルを持ってくる。 関係無いところがたまたまmoovになっているかもしれないので、周囲がスクリーンショットの雰囲気と似ているところ。 最後のほうにある。

先頭に戻り、「mdat」という文字列の4バイト前を、Shiftを押しながらクリック。 バイナリエディタが違って範囲選択の方法が違うなら、それに合わせて。 選択した部分の長さを確認。 HxDならステータスバーに出ている。 0x7F5473C9。

mdatの前の4バイトがmdatのサイズなので、このサイズに書き換え。 バイナリエディタによっては初期設定が上書きではなく挿入かもしれないので、Insertキーとかで切り替える。

保存して再生できたらOK。

あと、エンコード中にエラーで止まることもあった。 これはエンコード前にゆっくりMovieMaker4を再起動するようにしたら、見ることが無くなった。

さくらのVPSでディスクイメージを取得する方法

さくらのVPSで借りているサーバーを1台解約することにした。使わないから解約するのだけど、とはいえ、もしかするとまた必要になることがあるかもしれない。ファイルは全部保存しておくにしても、「動いていたサービスの様子を確認したいな」となったときのことを考えると、手元のVMで動かせるようにしておくのが一番である。

こういうときは、Mondo Rescueが定番で、方法を解説した記事もいっぱいある。でも、ちょいちょいハマリどころがある様子。自動化されているとはいえ、結局はファイルのコピーになるので、大量にファイルがあると重くないかとか、何かのバグで漏れたりしないかが心配。

せっかくの仮想サーバーなのだから、ストレージをそのままコピーしたい。が、その手段は用意されていない。「さくらのVPS ディスクイメージ」とかでググっていたら、下の記事が出てきた。クラウドに移行はできるらしい。

knowledge.sakura.ad.jp

途中に出てくるアーカイブをダウンロードすれば手元のVMでそのまま動かせるのでは? と試してみたら、上手くいったので紹介する。なお、お金が数百円掛かる。

上記の記事の通りにVPSディスクをソースとしてアーカイブを追加する。

f:id:kusano_k:20220314012324p:plain

適当に項目を入れて作成。20分~90分掛かるとのこと。200 GBなので、まあそんなものかと思ったら……。たしかに作成はすぐに終わる。20分も掛かっていなかったかもしれない。しかし、「作成」と「ダウンロード」の2段階あるらしく、この「ダウンロード」がとても遅い。なぜ? 数時間掛かっても終わらなくて、諦めて寝て、起きたら終わっていたくらいの感じ。

f:id:kusano_k:20220314012538p:plain

「コピー完了時点から課金開始」は良いのだけど、こっちは完了したらすぐダウンロードしたいのに、寝ていたら時間が空いてお金が掛かってしまう。

f:id:kusano_k:20220314012648p:plain

ダウンロードはSFTP。IDやパスワードなどが設定された curl のコマンドが出てくるので、それをそのままコンソールに貼り付ければ良い。ここのダウンロードは、少なくとも我が家の100 Mbpsの回線の速度はフルに出ていた。圧縮はされずストレージそのままなので、200 GBのストレージならそのまま200 GB。ダウンロードした後、サーバー側で消すのを忘れないように注意。

お値段はここVPSのストレージが200 GBだけど、100 GBの次が250 GBなので、250 GB分。11時間使って143円のところ、もう日額料金のほうが高いのでそれが適用され、137円。ダウンロードした後で消すのを忘れさえしなければ、たいした金額ではない。

ダウンロードしたファイルは生のディスクイメージ。VirtualBoxなら VBoxManage convertfromrawVM用のディスクに変換できる。

"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe" convertfromraw archive.img archive.vdi

あとは適当にVMを作って、このディスクを割り当て、ネットワークの設定を変更したら動いた。ずっと公開鍵を使ってSSHログインしていて、パスワードが分からず、ネットワークが繋がらないからSSHも繋がらず……で手間取った。確認しておきましょう。

ストレージは200 GB中50GBくらい使っていた。ダウンロードしたイメージを圧縮して取っておこうと思ったら、bzip2で200 GBが150 GBくらいにしか縮まなかった。ダウンロード前に dd if=/dev/zero of=zero bs=4k で空き領域を0で埋めておけば良かったかも。

他の方法

悪いこと言わないからMondo Rescueを使ったほうが良い。Mondo Rescueも試して、試した感じとても出来が良い。多少ファイルが多いとかくらいでトラブる作りにはなっていなそう。Mondo Rescueのほうは(空き領域は含まず)残っているファイルだけをアーカイブして、圧縮もしてくれるので、16 GBくらいになった。ちょいちょいハマるという話も、途中のfstabの編集でパーティションのUUIDを/dev/sda?に書き換えるのと、他の途中で書き換えることになるファイルで/dev/vda?を/dev/sda?にしたら動いた。

そういえば、ディスクイメージをそのままコピーしたほうがこの作業無しで動いているのはなぜなのだろう? パーティションのUUIDがそのままコピーされて、UUIDで指定されていればUUIDのほうが優先されるのか?

ダウンロードできるファイルって結局は/dev/vdaそのものなので、100円払わなくても、ddとsshで転送をするという手もある。試してはいない。今どきのファイルシステムなら動いている途中でコピーしたところで壊れはしないだろう。たぶん。怖いならsingle userモードという手もある。途中にgzipなどを挟むこともできる。

Amazon S3のストレージ料金を無料にする裏技

追記: 無料にならなそう。後半を参照。

Amazon Simple Storage Service。 ファイル(オブジェクト)を保存したり、配信したりできるクラウドサービス。

料金は細かく設定されていて、リクエストや転送帯域に関しても課金される。 タイトルで「ストレージ料金」と言っているのは、それらを全部ひっくるめた料金ではなく、狭義の、オブジェクトを保存していることに対して毎月掛かる料金。 最も安いS3 Glacier Deep Archiveでも、0.002USD/GB/月(東京リージョン、2022年1月現在)掛かる。 一見とても安く思えるが、例えば100 TBを10年保存しようと思うと、24,576ドル、約300万円にもなってしまう。 オブジェクトを保存したり取り出したりするときに金が掛かるのは諦めるとして、この保存に掛かる料金を無料にしたい。

はい。

f:id:kusano_k:20220126231820p:plain

ファイルサイズが0バイトなので、お金は掛からないはずである。

f:id:kusano_k:20220126232039p:plain

元のファイルの中身を分割してファイル名にしている。 手元のストレージで同じ事をすると、一見ファイルサイズが0に見えて、inode(ext4)やMFT(NTFS)の容量を消費するから、当然無限に保存できるわけではない。

オブジェクトを1個保存するたびにリクエスト料金が掛かるため、多数のオブジェクトに分割することによって、最初の保存時の料金が高くなる。 何年保存すれば得になるのか、損益分岐点を計算してみる。

素直にファイルをそのままオブジェクトとして保存したときは、最安で0.002USD/GB/月。

キー名(オブジェクト名)は、最大UTF-8で1024バイト。

オブジェクトキー名の作成 - Amazon Simple Storage Service

UTF-8として無効なキー名も使えないかな?」と思ったけど、少なくともAWS CLIでは弾かれた。 バイト列をUTF-8として有効なUnicodeに変換したときにどの程度の詰め込めるかに興味はあるものの……まあ、Base64で良いだろ。 Base64ではサイズが4/3倍に増える。 (コントロールパネルからはディレクトリっぽく見えるけど)S3のキー名にディレクトリの概念は無い。 元のファイル名と連番の部分で32バイト程度は使うだろうか。 1個のオブジェクトのキー名に、(1024-32)/(4/3)で744バイト分のデータを詰め込める。

ファイルの取り出しのLISTは1000件まとめて取得できるので、無視できる。 PUTリクエストは0.0047USD/1,000回。 1回、1バイトあたり6.3172×10-9USD。 ストレージ料金の「GB」が10003なのか10243なのか分からない。 10243とすると、6.783USD/GB。 ということで、3,392月=283年以上保存するならば、S3 Glacier Deep Archiveに保存するよりも、ファイル名にエンコードしたほうがお得💰💰💰


当然「本当に無料になるの?」というのが気になる。 料金の算出の元になるであろうバケットの合計サイズは「バケットメトリクス」で確認でき、これは1日1回更新される。 1日以上経ったので確認してみた。 オブジェクト数717個で、バケットサイズ0バイトになっていれば良い。

f:id:kusano_k:20220129002439p:plain

727.7 KB。 ダメそう😢

なぜファイルサイズが0バイトなのにバケットのサイズが増えているんだという話だが……。

メタデータもストレージ使用量の課金対象

kurochan-note.hatenablog.jp

ここで挙げられているメタデータだけで、1ファイル1 KBにはならないだろうし、ファイル名もメタデータ扱いなのかなぁ。

CloudWatchだとバイト単位の正確な値が確認できて、745,129バイト。 キー名の合計は733,657文字。 キー名の分を除くと、11,472バイト。 オブジェクト1個あたりちょうど16バイト。 切りが良いので、計算は合っていそう。

この16バイトが何なのかは謎。 ETagなのか。 あるいは、(特に設定していない)Content-Typeは text/plain の10文字だったから、それと LastModified か何かで6バイトなのか。

SECCON CTF 2021作問者writeup+作問した感想

CTF Advent Calendar 2021 22日目の記事です。

前日の記事は、Xornetさんによる「SECCON CTF 2021 作問者Writeup + 運営参加記」でした。 CTF Advent Calendarを眺めていたら、Xornetさんの投稿予定を見つけて、「その手があったな」と私も登録した。 作問ではお世話になりました。

明日の記事は、Satokiさんによる「【文学】 Flagを読む」です。 え、Advent Calendarは何か気の利いたことを言いながらパスを回すっぽいけど、タイトルを見ても何も分からない……。 IMCTF、楽しかったです。 ありがとうございました。

SECCON CTFは解く側でだいたいいつも参加している。 誘ってもらって、今年は作問側に回った。 チームで作問するのは初めての経験だったし、問題の解法はそこそこに、裏話とかを書いていこうと思う。 詳細な解法は英語版のほうを見てほしい。 英語、APIのドキュメントなどはあまり困らないけど、ニュース記事や日記は読めない。 書くのも同じで、裏話みたいなのを英語のほうに書くのは諦めた。

なお、Discordでアナウンスされたように、SECCON CTF 2021のスコアサーバーと問題サーバーは少なくとも年内いっぱいは動いている予定。 まだ見ていなくて年末に時間がある人は挑戦してほしい。

score.azure.noc.seccon.jp

s/<script>//gi (misc, 115 teams solved)

英語版: s///gi (misc, 115 teams solved) - HackMD

危険な文字列を除去したいというときにやりがちなミスとして、削除処理を1回しかしないというものがある。 例えば、 <SCR<script>IPT> から <script> を1回削除すると <SCRIPT> になり、危険な文字列が残ってしまう。 「ちゃんと <script> が無くなるまで繰り返してください」という問題。 ただし、入力は64 MiB。

O(n2) では24時間のコンテスト中に終わらない(はず)。 入力をスタックに順番に入れていって、 <script> になったら取り出すということを繰り返す。 1回の削除が O(1) になり、全体が O(n) で、適当に書いても1分も掛からずに終わる。

data = open("flag.txt").read()[:-1]

stack = []
for c in data:
  stack += [c]

  if "".join(stack[-8:]).lower()=="<script>":
    for i in range(8):
      stack.pop()

print("".join(stack))

CTFのジャンルのひとつにPPC(Professional Programming and Coding)というのがあり、その問題。 「PPCを出題するのはどうなのか?」ということで揉めた。 申し訳ない。 「セキュリティ何も関係無いだろ」「競技プログラミングでやれ」的な。 「出すなら、せめてPPC脆弱性を探す問題ではない)と分かるようにするべき」ということで、この問題だけ作問者でもwarmupでもない「ppc」というタグが付いている。

競技プログラミングをやっている人(と一部のアルゴリズム研究者くらい)しか知らないアルゴリズムは色々とあるだろうけど、この問題はそうではないと思っている。 ただ、まあ、競技プログラミングをやっていない人でも頑張れば解けるくらいの難易度になっている一方で、やっている人には簡単すぎるよな……。

競技プログラミングPPCよりもCryptoのほうが近いのではないかという気がしている。 中国剰余定理とかナップサック問題とかどちらでも見る。 この競技プログラミングの問題は、最初の文章の解読だけできれば、CTFのCryptoを解いている人はあっさり解けそう。

atcoder.jp

極論、Cryptoも競技プログラミングも、愚直には指数時間掛かる処理を多項式時間に落とせば解ける。 Cryptoを解いている人が競技プログラミングに手を出したり、逆のことをしてみると、楽しいのではなかろうか。

qchecker (misc, 14 teams solved)

英語版: qchecker (misc, 14 teams solved) - HackMD

f:id:kusano_k:20211222054558p:plain

eval$uate=%w(a=%(eval$uate=%w(#{$uate})*"");Bftjarzs=b=->a{a.split(?+).map{|b|b.to_i(36)}};c=b["awyiv4fjfkuu2pkv+awyiv4f
v                  ut                  71                  6g                  3j                  +a                  x
c  e5e4pxrogszr3+5i0o  mfd5dm9xf9q7+axce5  e4khrz21ypr+5htqqi  9iasvmjri7+axcc76i  03zrn7gu7+cbt4  m8  xybr3cb27+1ge6  s
n  jex10w3si9+1k8vdb4  fzcys2yo0"];d,e,f,  g,h,i=b["0+0+zeexa  xq012eg+k2htkr1ola  j6+3cbp5mnkzll  t3  +2qpvamo605t7j  "
]  ;(j=eval(?A<<82<<7  1<<86)[0])&&d==0&&  (e+=1;k=2**64;l=->  (a,b){(a-j.ord)*25  6.pow(b-2,b)%b  };  f=l[f,k+13];g=  l
[                  g,                  k+  37];h=l[h,k+51];i=  l[i,k+81];j==?}&&(  d=e==32&&f+g+h  +i  ==0?2:1);a.sub  !
(/"0.*?"/,'"0'+[d  ,e  ,f,g,h,i].map{|x|x  .to_s(36)}*?+<<34)  );srand(f);k=b["7a  cw+jsjm+46d84"  ];  l=d==2?7:6;m=[  ?
#*(l*20)<<10]*11*  ""  ;l.times{|a|b=d==0  &&e!=0?rand(4):0;9  .times{|e|9.times{  |f|(c[k[d]/10*  *a  %10]>>(e*9+f)&  1
)!=0&&(g=f;h=e;b.  ti  mes{g,h=h,8-g};t=(  h*l+l+a)*20+h+g*2+  2;m[t]=m[t+1]=""<<  32)}}};a.sub!(  /B  .*?=/,"B=");n=  m
.                  co                  un                  t(                  ?#                  )-  a.length;a.sub  !
("B=","B#{(1..n).map{(rand(26)+97).chr}*""}=");o=0;m.length.times{|b|m[b]==?#&&o<a.length&&(m[b]=a[o];o+=1)};puts(m))*""

Quineで実装したフラグチェッカー。 フラグを1文字与えるごとに状態が変化していくので、CORRECTと表示されるようなフラグを見つければクリア。

上の色はwriteup用に画像処理ソフトで塗っている。 本当はカラーで出力するようにしたかったけれど、横幅120文字に詰め込むことができなくて諦めた。

Reversingでは、何かのVM用のバイトコードとか、謎アーキテクチャのバイナリを解析する問題を良く見るけれど、ソースコードを解析する問題があっても良いのでは? という主張。

状態を保存する変数を先頭ではなく中央あたりに持ってきたり、単純には差分が取れないように SECCON の文字を回転させたりはしているけれど、まあ、そんなに難しくはない。 結局、フラグを整数として見たときの剰余がある値になっているかどうかを調べているだけなので、ソースコードを解読したら、あとは中国剰余定理。

で、なぜこれがreversingではなくmiscなのかというと、レビューで「reversing要素が少ないからすぐに解けた」と指摘されたから。 たしかに、半分reversingで半分cryto+一発ネタ感はある。 同時に「簡単だけど、見た目のとっつきづらさと暗号要素もあるから、正解者数は伸びないかも」とも言っていて、実際にその通りですごい。

Vulnerabilities (web, 94 teams solved)

英語版: Vulnerabilities (web, 94 teams solved) - HackMD

f:id:kusano_k:20211222235528p:plain

Go製のウェブアプリ。

{
  "Name": "x",
  "name": "",
  "ID": 14
}

で解ける。

当初はWebの問題が少なく。Warmup枠が無かった(いや、正確にはCookie Spinnerが当初はwarmup枠だった。warmup……? 🤔)ので出題してみた。 最初は Name のチェックが無かったけど、「warmupとはいえ、こんなやるだけの問題はダメ」と言われた。 ボツは悲しいので、「何かもう一ひねりできないかな~」とGinやGORMのソースコードを漁ったが、Goはしっかりしていてつらかった。 JavaScriptなら、適当に違う型の値でも突っ込めば何とでもなりそうなのに。 静的型付け言語強い。 Gin(が中で使っているencoding/json)がなぜか大文字小文字を無視していたので、これで。 別に区別する必要性は無さそうなものだけど……JSONはキー名に小文字を使うことが多く、Goの構造体のフィールド名は先頭を大文字にしないといけないからだろうか。

フロント側が適当でも問題としては成立するけれど、やはり良い見た目にしたい。 最初はときどき撮っていた花の写真でも使おうかなと思ったけど、写真だけ撮っていて名前が分からないのでダメだった。 ふと脆弱性のロゴを見てみたらたいていCC0だったので、テーマを脆弱性にして、ありがたく使わせてもらった。 ちなみにデザインはPure CSS

https://purecss.io/layouts/blog/

sed programming (reversing, 14 teams solved)

英語版: sed programming (reversing, 14 teams solved) - HackMD

$ echo "SECCON{dummy}" | sed -f checker.sed
WRONG
#!/bin/sed -f

# Check flag format
# Some characters are used internally
/^SECCON{[02-9A-HJ-Z_a-km-z]*}$/!{
  cINVALID FORMAT
  b
}

:t
s/1Illl11IlIl1/1IlIl11Illl1/;tt
s/1Illl11III1/1III11Illl1/;tt
s/1Ill11IlIl1/1IlIl11Ill1/;tt
s/1Illl11l1/1l11Illl1/;tt
 :
s/o/1IlIl11IIll11IIll11IlIl11IIll11IIll11IIll11IIll1/;tt
s/O/1IlIl11IIll11IlIl11IlIl11IIll11IIll11IIll11IIll1/;tt
s/j/1IlIl11IIll11IIll11IlIl11IIll11IlIl11IIll11IlIl1/;tt
s/^/1IIll11IIll11IlIl11IIll11IIlI11l1/;tt

sedスクリプト

sedは単に正規表現で置換をするだけだと思っている人も多い……というか私もそう思っていて、「s/<script>//giにnaive解を付けたほうが良いかな? ワンライナーで書けないかな?」と調べていて、ちょっとしたプログラミング言語くらいの機能があることを知った。 info sed と打ってみると分量がすごい。

もっとも、 sed の機能はほとんど使っておらず、(空文字列を置換対象にできなかったので使った ^ 以外は)正規表現すら無しで、ひたすら置換を繰り返しているだけ。 マルコフアルゴリズム

マルコフアルゴリズムチューリング完全性をアピールしたいと思い、1次元セルオートマトンを実装している。 あるシステムAを使ってチューリング完全なシステムBをシミュレートできるならば、システムAはチューリング完全である。 入力を13世代進めて、特定の文字列になっていれば、正解。

作るのも面倒だったけど、解くのはもっと面倒だろうに、解いているチームはすごい。

constellations (reversing, 17 teams solved)

英語版: constellations (reversing, 17 teams solved) - HackMD

Goアプリの解析。 実行するとフラグが出てくるけど遅い。 高速化すればOK。

Goはネイティブコードに変換される。 綺麗に戻してくれる逆コンパイラは無いはず(あったら教えてほしい)。 Goのランタイムも入ってサイズは大きいけれど、シンボルは残しているので、 main.main から読んでいけば読めるはず。

……ということを改めて確認していたら、デバッグ情報としてソースコードが残っていることに気が付いた😩 GDB+PEDAで見ていると普通に表示されていた。 作問時に確認したときはコンソールを小さくでもしていたのだろうか?

追記: ソースコードは、バイナリの中にあるのではなく、単に私の手元にあるソースコードが表示されているだけだった。 良かった。

この問題だけフラグが妙に長いのは、フラグの各文字に対応する文字列をデバッガから1個ずつコピペするのではなく、そこも自動化してくれということである。 たとえフラグが普通の長さでも、結果的にはそのほうが早いと思う。

Average Calculator (pwnable, 56 teams solved)

英語版: Average Calculator (pwnable, 56 teams solved) - HackMD

良くあるスタックバッファオーバーフローpwnable。 ただし、スタックの各値が64bitのところ、32bitちょっとしか書き込めないので、そこを何とかしてくださいという問題。

ROPで scanf("%lld") を呼び出して、GOT overwriteをすれば良い。

平均を取っているのは、「オーバーフローを防ぐために32bit程度に制限しているんですよ」と言いたいだけであって、何の意味も無い。 何の意味も無かったのだけれど…… @moratorium08さんが、「これ、FullRELROでも解けない? i を書き換えて __libc_main のアドレスのところを飛ばすようにすれば、 sum__libc_main ±任意の値にできるから、それで system にして、あとはスタックを上手く調節すれば~」と言っていて、「平均にちゃんと意味が出てきた!」と色めき立った。まあ、冷静に考えると、__libc_main を書き換えないとROPができないので無理だったのだが。この手法では無いけれど、@moratorium08さんは結局FullRELROでも解いていたので、腕に自信のある人は挑戦してみてほしい。

感想

振り返ってみると、簡単な問題か変な問題しか作っていないな……。 作問をしてみて分かったけど、そのジャンルを極めるくらいじゃないと、良い問題は作れませんね。 「良い問題とは?」という話がそもそもあるけれど……後から「○○CTFの××の手法で解ける」みたいに語られるのは良い問題だろうか。 いつかはそういう問題を作りたい。

作問を引き受けた理由で一番大きいのは、どんな風に作問をするのか知りたかったから。

テンプレートに「 docker-compose up で問題サーバーが立ち上がるようになっていること」みたいなことが書かれていて、「は~、今どきはプルリクがマージされたら自動で問題サーバーが立ち上がるのかな。すげ~」と思っていたけれど、そこは作問者にサーバーが割り当てられて、手動だった😇 私の手元のTera Termでは、今も問題サーバーのログが流れている。 まあ、問題によっては自動化できない部分もあるだろうし、どの問題でも緊急対応をしないといけない可能性もあるし。

もっとも、作問者の手作業が発生するのはサーバーの管理くらいで、あとのソルバーによるチェックとか配布ファイルを固めるのとかは自動化(もしくはインフラチームの頑張り)されていた。 問題のレビューと合わせてそこら辺もチェックされるだろうから、CTFでときどき見かける「配布ファイル間違えてました。ごめんね」というミスも発生しづらそう。

SECCONのカンファレンスにCTFのインフラチームの発表もあって、ちょっと雰囲気が分かるかもしれない(事前に申し込んだ人だけの限定公開だったけど、なぜか公式Twitterがツイートしているので見られる)。 ↓の15分くらいから。

相互にレビューをするので、未公表の問題を何日も掛けてじっくりと解けるのも、人によっては嬉しい特権だろうか。 今回のCryptoはなぜかやたらとLLLが出題されていたけれど、そのおかげでLLLがだいぶ理解できた。 最初は非想定解法もけっこう残っているので、非想定解法を探すのが好きな人も楽しいかもしれない。