Chinachuで録画サーバーを作った

https://chinachu.moe/

今まではメインで使っているデスクトップPCでアニメを録画していた。 特に土日の夜にアニメが集中していてPCを再起動することができずに不便。 録画専用のサーバーを作ることにした。

DockerやAnsibleでのコード化はしていてないので、自分のためにも過程をメモしておく。

ハードウェア

今ではプレミア価格になっているPT3が2枚付き。 ベースはProLiant MicroServer。 音はとても静か。 CPUが非力なので、ブラウザの視聴はカクカクで使い物にならなかった。

ICカードリーダーは、SCR3310-NTTCom。 確定申告の電子申請にも使える。

録画用に4 TBのHDDを増設した。 「Linux HDD増設」とかでググって使えるように。

概要

そもそも、ChinachuとMirakurunの関係が分かっていなかった。 ChinachuはMirakurunに放送の録画を投げる。 Mirakurunは複数のソフトから依頼を受け付けて、録画デバイスを上手いこと使い分ける。 EPG番組表の取得もMirakurunの仕事。 Mirakurunはさらに録画コマンドに処理を投げる。 下のレイヤーから作業を進めると動作確認ができて良い。

OS

ネットの記事を見るとUbuntuの例が多い。 Ubuntuを適当にインストールしてSSHで繋げられるようにする。

ドライバ

カードリーダーは標準ドライバで動く。 aptで何をインストールしたか忘れたけど、pcsc_scanJapanese Chijou Digital B-CAS Card (pay TV)と出てくれば良い。

PT3も標準ドライバで動く。 /dev/dvb/adapter?がPT3の枚数×4個見えれば良い。 偶数が衛星波、奇数が地上波。 使うドライバによって、使う録画コマンドが異なる。 標準ドライバのほうが安心だと思ったけど、PT3専用のドライバのほうが使っている人が多くて楽だったかもしれない。

録画コマンド

recdvb(PX-BCUD対応版)のインストール « » Sat's space

recdvbをインストール。 この記事に載っているコマンドで視聴可能な.tsファイルが生成されれば良い。 ただ、このままだとちょっと問題がある。

recdvbrecdvbchksigSNRが取得できない。

USB接続で地デジ4チャンネル録画できるチューナーPX-Q1UDを買ったそして試した - Write and Run

この記事のパッチを当てる。

BSプレミアムなどの番組情報が取得できない(録画もできない?)。PT3にTSIDを渡さないといけないチャンネルがあるらしい。TSIDが何で、どこのレイヤーで効いているのか、分からん。

PT3 + recdvb + MirakurunでNHK BSプレミアムのEGPが取得できない問題に対応 - I am a wannabe.

PT3環境でmirakurun と http://www13.plala.or.jp/sat/recdvb/recdvb-1.3.1.tgz の組み合わせでEPGが取得できない問題に対する修正

このパッチを当てて、さらにコメント欄の修正を加える。 この辺りは4月以降のBSチャンネル再編で修正が必要になりそう。

Mirakurun

https://github.com/Chinachu/Mirakurun

npmに登録されている。 が、一部のチャンネルで録画したファイルがTVTestで視聴できなかった。

PATの書き換えでTSパケットの空きを0xffで埋めるようにした by kusano · Pull Request #34 · Chinachu/Mirakurun

この修正を加えたら、TVTestでも見られるようになった。 手元でビルドしてインストール。 手順は、

https://github.com/Chinachu/Mirakurun/blob/master/.github/CONTRIBUTING.md

に載っている。

mirakurun config [server|tuners|channels]でそれぞれ設定。

serverは初期設定のまま。

tuner。

- name: PT3-S1
  types:
    - BS
    - CS
  command: recdvb --dev 0 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-S2
  types:
    - BS
    - CS
  command: recdvb --dev 2 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-S3
  types:
    - BS
    - CS
  command: recdvb --dev 4 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-S4
  types:
    - BS
    - CS
  command: recdvb --dev 6 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-T1
  types:
    - GR
  command: recdvb --dev 1 <channel> - -
  decoder: arib-b25-stream-test
  isDisabled: false

- name: PT3-T2
  types:
    - GR
  command: recdvb --dev 3 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-T3
  types:
"/usr/local/etc/mirakurun/tuners.yml" 60L, 1127C
- name: PT3-S1
  types:
    - BS
    - CS
  command: recdvb --dev 0 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-S2
  types:
    - BS
    - CS
  command: recdvb --dev 2 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-S3
  types:
    - BS
    - CS
  command: recdvb --dev 4 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-S4
  types:
    - BS
    - CS
  command: recdvb --dev 6 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-T1
  types:
    - GR
  command: recdvb --dev 1 <channel> - -
  decoder: arib-b25-stream-test
  isDisabled: false

- name: PT3-T2
  types:
    - GR
  command: recdvb --dev 3 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-T3
  types:
    - GR
  command: recdvb --dev 5 <channel> - -
  decoder: arib-b25-stream-test

- name: PT3-T4
  types:
    - GR
  command: recdvb --dev 7 <channel> - -
  decoder: arib-b25-stream-test

- name: TBS6922
  types:
    - SKY
  command: szap-s2j -c /usr/local/etc/szap-s2j.conf -l <satelite> -S 1 -p -r -M 5 -C 35 <channel>
  dvbDevicePath: /dev/dvb/adapter0/dvr0
  decoder: arib-b1-stream-test
  isDisabled: true

recpt1の代わりにrecdvbを使うように変更。

channels。

うちのマンションはケーブルテレビでテレ玉チバテレビも入るので、チャンネルスキャンをする。

curl -X PUT "http://localhost:40772/api/config/channels/scan"

このコマンドを実行するとchannelsの設定が上書きされて空行などが消えるので注意。

Chinachu

Wikiのインストール手順の通り。

https://github.com/Chinachu/Chinachu/wiki/Gamma-Installation-V2

録画先を増設したHDDにして、ファイル名を<title> [<channel-name>][<date:yyyymmdd-HHMM>].tsにした。

http://サーバーのIPアドレス:20223を開いて、番組表が見られて、録画ができれば良い。 同じLAN内からは繋がる。

nginx

外からも操作できるようにしたい。 DDNSで自宅に繋がるようにはしている。

一応Chinachu単体でも、wuiUserswuiOpenServerwuiOpenPortを設定すれば外からアクセスできるのだが、廃止予定らしい。 nginxでリバースプロキシを立てる。

aptでインストールして、 /etc/nginx/sites-enabled/default を削除。 /etc/nginx/conf.d/に適当なファイル名で、

server {
  listen 80 default_server;
  location / {
    auth_basic "secret";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_pass http://サーバーのIPアドレス:20772;
  }
}

を書き込む。 htpasswd -c /etc/nginx/.htpasswd ログインIDでパスワードファイルを作成。

HTTPS

録画サーバーに平文で繋ぐのが憚られるような信用できないネットワークを使う機会があるか……?とは思うが、このご時世、webサーバーを立てたらHTTPSにもしておこう。

https://certbot.eff.org/

で、nginxとOSを選択。 出てきたコマンドを入力すると、証明書の取得、nginxの設定の変更、証明書の更新をcronに登録までやってくれる。 すごい。

Samba

メインのWindows PCから録画したファイルを見られるようにする。 sudo apt install sambaでsambaをインストールして、/etc/samba/smb.confに次の設定を追加。

[video]
  path = 録画ディレクトリ
  browsable = yes
  guest ok = no
  read only = no
  writable = yes

sudo smbpasswd -a ユーザー名

録画予約

この時期にいちいちアニメのタイトルを登録して、表記揺れに悩まされたりするのが面倒。 ジャンルアニメを全部録画したらどうなるのだろうと試してみたところ、1週間で1 TBくらいだった。 4 TBのHDDではちょっと足りない。 また、地上波と衛星波がそれぞれ4 chずつだと、数番組は予約が被って録画ができなかった。 ちまちまと登録するしかない。

テキストエディタに回転表示機能があったら便利なのではないか

C言語でこういうコードがあったとする。

int f(Z *z) {
    int result;
    W w;
    X x;
    Y y;
    Z z;
    result = hoge(&w);
    if (result != E_OK)
        return result;
    result = fuga(&w, &x);
    if (result != E_OK)
        return result;
    result = piyo(&w, &x, &y);
    if (result != E_OK) {
        /* piyoのエラーはログを出す */
        log("error in piyo !!!! code: %d, arg: (%s, %s)", result, W2str(&w), X2str(&x));
        return result;
    }
    result = hogera(&y, &z);
    if (result != E_OK)
        return result;
    return E_OK;
}

Erlangで同じコードを書くと、Erlangは途中でreturnすることができないので、こうなる。

-spec f() -> {ok, z()} | {error, term()}.
f() ->
    case hoge() of
        {ok, W} ->
            case fuga(W) of
                {ok, X} ->
                    case piyo(W, X) of
                        {ok, Y} ->
                            case hogera(Y) of
                                {ok, Z} ->
                                    {ok, Z};
                                {error, Reason} ->
                                    {error, Reason}
                            end;
                        {error, Reason} ->
                            %% piyoのエラーはログを出す
                            log("error in piyo !!! code: ~p, arg: (~p, ~p)", [Reason, W, X]),
                            {error, Reason}
                    end;
                {error, Reason} ->
                    {error, Reason}
            end;
        {error, Reason} ->
            {error, Reason}
    end.

ネストが深いし、関数の呼び出しとエラー処理が離れていて読みづらい。 returnのある言語でも、single-entry single-exitルールで書いていたり、JavaScriptのいわゆるコールバック地獄になったりすると、同じようになる。

エラー処理が離れている点については、エラー処理を先に書くという手がある。

-spec f() -> {ok, z()} | {error, term()}.
f() ->
    case hoge() of
        {error, Reason} ->
            {error, Reason};
        {ok, W} ->
            case fuga(W) of
                {error, Reason} ->
                    {error, Reason};
                {ok, X} ->
                    case piyo(W, X) of
                        {error, Reason} ->
                            %% piyoのエラーはログを出す
                            log("error in piyo !!! code: ~p, arg: (~p, ~p)", [Reason, W, X]),
                            {error, Reason};
                        {ok, Y} ->
                            case hogera(Y) of
                                {error, Reason} ->
                                    {error, Reason};
                                {ok, Z} ->
                                    {ok, Z}
                            end
                    end
            end
    end.

見た目も元のC言語のコードに近い。 ただし、ネストの深さは変わらない。 C言語では上から下に読めるが、Erlangでは左上から右下に読むことになる。

エディタがこのように斜めに表示してくれれば、ネストの深さの問題が解決する。

f:id:kusano_k:20180310171629p:plain

「Google にソフトウェアエンジニアとして入社して10年と10日がたちました」のクイズを解いた

Google にソフトウェアエンジニアとして入社して10年と10日がたちました — hayato.io

面白かったので、今からでも挑戦してみてほしい。 プログラミングが大変なのは最初のステップだけで、あとはひらめき。 下に解法を書く。 出題者のきらきら☆はやとたんさんの許可は得ています。

































































問題の画像はジュリア集合。 また、PNG内にJuliaというkey名で、left=0.0,top=-0.75,width=1.5,height=1.5,c=-0.?????-0.?????iというコメントがある。

一部は伏せ字でパラメタが与えられているので、伏せ字の部分を計算すれば同じ画像が生成できそう。 ここが一番大変だった。 全探索は無理なので、上位桁だけ探索して、差分が小さい値の下位桁を探索したり、画像の一部だけで差分を計算したりした。 伏せ字の部分の答えは、c=-0.75037-0.11177i。 次のプログラムで問題の画像と(ほぼ)同じ画像が得られる。

from PIL import Image
img = Image.new("RGB", (797, 797))
for y in range(797):
  for x in range(797):
    p = (x/797.*1.5)+(y/797.*1.5-0.75)*1j
    c = 0
    while c<255:
      p = p*p + (-0.75037-0.11177j)
      if abs(p)>2.:
        break
      c += 1
    img.putpixel((x, y), (c, c, c/2+128))
img.save("ans.png")
print "".join(map(chr, C))

f:id:kusano_k:20180228183855p:plain

この画像と問題の画像には差がある。 問題の画像を拡大して良く見ると、ポツポツと点が見える。 ジュリア集合にはこのような点は無いはず。

f:id:kusano_k:20180228184425p:plain

問題の画像と本来のジュリア集合で差があるところの差分を調べると次のようになっている。

32, 68, 68, 32, 32, 68, 50, 32, 32, 32, 32, 50, 32, 32, 32, 32, 32, 32, 32, 50, 32, 32, 32, 32, 32, 32, 32, 32, 48, 50, 50, 32, …

値の範囲がASCIIコードっぽいので、文字に直す。

 DD  D2    2       2        022    A 22 2  0 1   20 00  0      2  2 0     D D         2 0        60 2    0   4    7  0   0  6  002  2 02  63      222 72   2   22      2  0 0             2   6 2     00 2                0   0  0  0          D  2   0  2 7  F  2     2   4    C        0            7       0   02  D  D     2         0 0     0  2 0    6  02002 2   2   2 00    0C 1 3      0     2D   2D2 2  0  0   C   F     022 0   1  02      0  6             32     2        2  B2       22      6                    020   0      2  02  0    3     2       D2        2  00   0E         0   87   0 20     EF     2    2 34  1    2   D2  D      DDD  00    2  6    0   2     6 2   2         2   0    6 3 93 2 0  0    2     D 2  20 2  2   6 2   2   0      2  200   2   200       0    30 0  0   2 DD        2       0  2  0    20        0  02    0 6             23               2  2            20       0 0    0          2   2   22             6 20   2                 20      7         22 6    0     2 2     0   22    3    A  22       D  DD   D        A00 E 0 2      2  9 22 2          02   0      3  6      0       D  2 D   2  2  7 2             F7 50              0 0     4  1  370 0      2 2     D 2 0        0   2 0   00 0 4    200 0      0  2202 0  2  38  2 2    DD 2 2    22     2  0C    2   0        2                2       3 83936  2    C2  D  2  D  D   00    2  7  2  2  20 220  0    20 0        2  2     3    00       2  D22   2    2      7 0   2 2  7 0    2        0B  200 0  02 73     02   2        D    D 2 200 2 0      2   2   7      222          2 2        0 3   20 00    D 22 2DD2 0      2 22    0 0    2     0         2          0  3 6 5C 00   A 2D 2D  D 2 D 2 0      0      020      7 220 000   2072 2       7   37 2  2 0     D     D 22 0     0 2 63200 2   0 04 4    0 22     2   0  0          022 22  D 2    D    D     2    2 2  2 0 2   260 20   2   A      0 0    2 9 2    0        D       D  22   0  0     20     2  8 6 2 2 0     07       2     6  3     0    D    D  2       22     11       0     8    2      2 2 0          4     2        22   2    2      2 20 72 0      0        0   0   0   2 2      2  3   2000 0 2  DD        DD      2 2     2 2   2            2   2 0     0  2  2    6       B       D  2D    2           02    0 0   00  0      0               7             22           0 0  0   4   7    2  2 7 7   2         00     0 0    34 0   2 2 2    2   DD    2 20  000          2 0 4             9 22 0      6 3 7     2 2     DD     2  20 002  22       00 0 C   6  0 020027 2 7   2  02 2    33  2   0    D   22 2D   202  0   27        20 2   72 22  20    9     2   0  0      2  2 2 222 D      2   0   200      02       6 6       2        02 2     3   5   0     D D   D    D  22   0           0    6   0  0         0  2      5  3   2  20   D      22D2 2  0  02      02C           9   2 2        0 00         493 0     D2     DD 2   D   2     0     2   0   5   2      0C 0 6    2    2   536   0        2     D 2D2 20     225  00 0        5        2 2  F   2   0   0      2   002   D  2 D DD2  0  2     602   2    0        00 002  6  2  0 0        1    0        22D      D    0       0    2 2    8 702     2      070      6 1 8 00  22    2 2  2  2 2   2   02       A0  2 2   6  20        2      20220 1     3   0 2 D D  D  2      0       00 2   200    0      0    00    2   0 0      3 31   2 0  D  D  D  D DD     2 0      27           4 22 2   200 7 2C2  2       6 32     2  2 2      22  2     22 2    C0  0   2            0   50       20 63   2 0     2    2 2      2  2        0         A02    0            0 0    0 725 4         D D D D  2       0 0  0  C2   22   07  9  0  2   2 2  72    2     3    3  2 0  2       D  D     20     0   02  2 0  2 6           0        0        66   0    D     2           22  22  0     02   0   622  00   2  2   2    2 4 6        2   2 D  D    2   0    2  0  2  0 02     0   000   20  2     0   00 684       0   2     2            0 0        0    2         0   2   F 020       5   63    2          D D2 D         02   22  2 0   2  28   2        2      0 22           0 0    2   2        002    26    2  2   0  0 2220  0     02    2          5      2  2 DDD   D 2       0  0    020 00 0 2   2    2     0        2     3    0   22  DD   2     D  202 022    00  202   C  E 0 002  2  0277    2    0        2 0 00D2 2   2D         2 0     2   02 2    7  70 2 02   20 4     0 2 2  8  5  0     2D   2  2        00   2            22            0    6 0A   0000  3    2   0   2            D0   22  2 1  202  0 0  26  2 0     202    720   2  3   3   2   2    2 DD   D2 22  2       02   0 22     6   2         9 2  222       6   202  0     D     2  D  2  00  0  0  02 0  0   390  0   2    6 0 0        3   6 0  22   2 D     2  2 0     2 0      2   0      62   2 00   5D        0   335        2 D      D2 DDD2      0 2 7  0 00  2          20         2C 0  22  6     0 2 00  2D 2       2   2    20   22  22   2  67             2     2 00  63 3   2    2 D  D   2      B        02  0 2    0 20  6F 20     0 0 6       2   09  21   0 0         2D   20     20 2 4  2 0        2F 0 2 2  2 2  50   00   2    3       2   2  2 D  D   A   0 0 20    0  0 220   E6   0     2   2 0  2    0     3 0  22       D        7  0      0   2 2 0       4       2 0    2     2 0 4  6  2    0   D  D22 D2    C20 2        20       0   6 2  0  0     E 2 2   02   6 53 2    0 2  2    2  2          0        22 0 2   5 0 22               0 0        0     0     D 2 D  D 2  2   2 2  0    0   2    2720  0     5   0   2   72       2 0   D        22D      0  0     0 0 2         5 2    2 2  0  2   0 2   666    0                 2D 0         6         00    2 0      0 4      02 0    3    22   22         DD D  22 2  2 0        0  2 2   E0 00          0 0   22            2       2  D 22      0  0       200 0  209         0020   0      00       0          D    22    0          9 2  2 2      202 22     0       2 02 2      6  0  000      D  DDD      2            2       0         7  6      2202     6    2 22         D        0   2  24  0   00 2   646 70  20 C  8   220 0   C   63 7 0      2 22    22 2         2 3 22     20     22C   0 2 2  6  0     0   3   3       2  D    2  D2    2 0        0       00  D 2 202     26  2   0 22 0     6   000     D   B   D   0     2  6 0       22 27  00  0       6    222   A  12  C   2 20  22    

ここでだいぶ悩んだ。 ジュリア集合との差分で値を埋め込む方式では、元の画像が明るいところには値を埋め込めないので、値が埋め込まれている位置には意味が無い……?とか、空白と16進数の文字で出現頻度に偏りがあるが……?とか考えた。

この文字列の長さは6241文字である。 ところで、問題の画像のサイズは797x797ピクセルだった。 なぜ800x800ではなく、こんな中途半端な値なのだろう? 797は素数だった。 また、6241は792素因数分解できる。

ということで、この文字列を79文字ごとに改行して正方形にしてみる。

 DD  D2    2       2        022    A 22 2  0 1   20 00  0      2  2 0     D D  
       2 0        60 2    0   4    7  0   0  6  002  2 02  63      222 72   2  
 22      2  0 0             2   6 2     00 2                0   0  0  0        
  D  2   0  2 7  F  2     2   4    C        0            7       0   02  D  D  
   2         0 0     0  2 0    6  02002 2   2   2 00    0C 1 3      0     2D   
2D2 2  0  0   C   F     022 0   1  02      0  6             32     2        2  
B2       22      6                    020   0      2  02  0    3     2       D2
        2  00   0E         0   87   0 20     EF     2    2 34  1    2   D2  D  
    DDD  00    2  6    0   2     6 2   2         2   0    6 3 93 2 0  0    2   
  D 2  20 2  2   6 2   2   0      2  200   2   200       0    30 0  0   2 DD   
     2       0  2  0    20        0  02    0 6             23               2  
2            20       0 0    0          2   2   22             6 20   2        
         20      7         22 6    0     2 2     0   22    3    A  22       D  
DD   D        A00 E 0 2      2  9 22 2          02   0      3  6      0       D
  2 D   2  2  7 2             F7 50              0 0     4  1  370 0      2 2  
   D 2 0        0   2 0   00 0 4    200 0      0  2202 0  2  38  2 2    DD 2 2 
   22     2  0C    2   0        2                2       3 83936  2    C2  D  2
  D  D   00    2  7  2  2  20 220  0    20 0        2  2     3    00       2  D
22   2    2      7 0   2 2  7 0    2        0B  200 0  02 73     02   2        
D    D 2 200 2 0      2   2   7      222          2 2        0 3   20 00    D 2
2 2DD2 0      2 22    0 0    2     0         2          0  3 6 5C 00   A 2D 2D 
 D 2 D 2 0      0      020      7 220 000   2072 2       7   37 2  2 0     D   
  D 22 0     0 2 63200 2   0 04 4    0 22     2   0  0          022 22  D 2    
D    D     2    2 2  2 0 2   260 20   2   A      0 0    2 9 2    0        D    
   D  22   0  0     20     2  8 6 2 2 0     07       2     6  3     0    D    D
  2       22     11       0     8    2      2 2 0          4     2        22   
2    2      2 20 72 0      0        0   0   0   2 2      2  3   2000 0 2  DD   
     DD      2 2     2 2   2            2   2 0     0  2  2    6       B       
D  2D    2           02    0 0   00  0      0               7             22   
        0 0  0   4   7    2  2 7 7   2         00     0 0    34 0   2 2 2    2 
  DD    2 20  000          2 0 4             9 22 0      6 3 7     2 2     DD  
   2  20 002  22       00 0 C   6  0 020027 2 7   2  02 2    33  2   0    D   2
2 2D   202  0   27        20 2   72 22  20    9     2   0  0      2  2 2 222 D 
     2   0   200      02       6 6       2        02 2     3   5   0     D D   
D    D  22   0           0    6   0  0         0  2      5  3   2  20   D      
22D2 2  0  02      02C           9   2 2        0 00         493 0     D2     D
D 2   D   2     0     2   0   5   2      0C 0 6    2    2   536   0        2   
  D 2D2 20     225  00 0        5        2 2  F   2   0   0      2   002   D  2
 D DD2  0  2     602   2    0        00 002  6  2  0 0        1    0        22D
      D    0       0    2 2    8 702     2      070      6 1 8 00  22    2 2  2
  2 2   2   02       A0  2 2   6  20        2      20220 1     3   0 2 D D  D  
2      0       00 2   200    0      0    00    2   0 0      3 31   2 0  D  D  D
  D DD     2 0      27           4 22 2   200 7 2C2  2       6 32     2  2 2   
   22  2     22 2    C0  0   2            0   50       20 63   2 0     2    2 2
      2  2        0         A02    0            0 0    0 725 4         D D D D 
 2       0 0  0  C2   22   07  9  0  2   2 2  72    2     3    3  2 0  2       
D  D     20     0   02  2 0  2 6           0        0        66   0    D     2 
          22  22  0     02   0   622  00   2  2   2    2 4 6        2   2 D  D 
   2   0    2  0  2  0 02     0   000   20  2     0   00 684       0   2     2 
           0 0        0    2         0   2   F 020       5   63    2          D
 D2 D         02   22  2 0   2  28   2        2      0 22           0 0    2   
2        002    26    2  2   0  0 2220  0     02    2          5      2  2 DDD 
  D 2       0  0    020 00 0 2   2    2     0        2     3    0   22  DD   2 
    D  202 022    00  202   C  E 0 002  2  0277    2    0        2 0 00D2 2   2
D         2 0     2   02 2    7  70 2 02   20 4     0 2 2  8  5  0     2D   2  
2        00   2            22            0    6 0A   0000  3    2   0   2      
      D0   22  2 1  202  0 0  26  2 0     202    720   2  3   3   2   2    2 DD
   D2 22  2       02   0 22     6   2         9 2  222       6   202  0     D  
   2  D  2  00  0  0  02 0  0   390  0   2    6 0 0        3   6 0  22   2 D   
  2  2 0     2 0      2   0      62   2 00   5D        0   335        2 D      
D2 DDD2      0 2 7  0 00  2          20         2C 0  22  6     0 2 00  2D 2   
    2   2    20   22  22   2  67             2     2 00  63 3   2    2 D  D   2
      B        02  0 2    0 20  6F 20     0 0 6       2   09  21   0 0         
2D   20     20 2 4  2 0        2F 0 2 2  2 2  50   00   2    3       2   2  2 D
  D   A   0 0 20    0  0 220   E6   0     2   2 0  2    0     3 0  22       D  
      7  0      0   2 2 0       4       2 0    2     2 0 4  6  2    0   D  D22 
D2    C20 2        20       0   6 2  0  0     E 2 2   02   6 53 2    0 2  2    
2  2          0        22 0 2   5 0 22               0 0        0     0     D 2
 D  D 2  2   2 2  0    0   2    2720  0     5   0   2   72       2 0   D       
 22D      0  0     0 0 2         5 2    2 2  0  2   0 2   666    0             
    2D 0         6         00    2 0      0 4      02 0    3    22   22        
 DD D  22 2  2 0        0  2 2   E0 00          0 0   22            2       2  
D 22      0  0       200 0  209         0020   0      00       0          D    
22    0          9 2  2 2      202 22     0       2 02 2      6  0  000      D 
 DDD      2            2       0         7  6      2202     6    2 22         D
        0   2  24  0   00 2   646 70  20 C  8   220 0   C   63 7 0      2 22   
 22 2         2 3 22     20     22C   0 2 2  6  0     0   3   3       2  D    2
  D2    2 0        0       00  D 2 202     26  2   0 22 0     6   000     D   B
   D   0     2  6 0       22 27  00  0       6    222   A  12  C   2 20  22    

これを縦に読むと2がだいたい交互に出てくることに気が付く。 縦に読んで、空白を削除すると、

2B2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2B0A7C20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020207C0A7C202020202020202020202020202020202020202020202020202020202020436F6E67726174756C6174696F6E7321202020202020202020202020202020202020202020202020202020202020207C0A7C20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020207C0A7C20202020202020202020202020202022446F20746865207269676874207468696E672E204D61792074686520636F6465206265207769746820796F752E222020202020202020202020202020207C0A7C20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020207C0A7C20202020202020202020202020202020202020202020202020205468616E6B20796F7520666F7220796F75722074696D652E2020202020202020202020202020202020202020202020202020207C0A7C20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020207C0A7C20437265617465642062792062383630636132383364303135643833396631303433313237353336666232393063373438646665353938373439613635323363623130663635653031323561207C0A7C20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020207C0A2B2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2B

となる。16進数として文字列に直すと、

+-----------------------------------------------------------------------------+
|                                                                             |
|                              Congratulations!                               |
|                                                                             |
|               "Do the right thing. May the code be with you."               |
|                                                                             |
|                          Thank you for your time.                           |
|                                                                             |
| Created by b860ca283d015d839f1043127536fb290c748dfe598749a6523cb10f65e0125a |
|                                                                             |
+-----------------------------------------------------------------------------+

「画像に隠されたメッセージ」はDo the right thing. May the code be with you.なので、私(@kusano_k)の場合は、

$ echo -n 'kusano_k' 'Do the right thing. May the code be with you.' | sha256sum
ffc5b750d7a37441f98ca4f9e4912bf7ad956934a4e9b4fe1740a935bd056235  -

10周年おめでとうございます!

NEETCOINに名前を刻んだ

以前ビットコインで、Ghash.I.O.というマイナーがハッシュレートの50%以上を占めて「これはヤバイ」と話題になったことがある。

ビットコインが「終了」の危機に瀕している ひとりの山師のハッシュレートが51%を超えたため - Market Hack

ただ、この記事は間違いで、検証作業は参加者全員が行っている。Ghash.I.Oが悪意を持ったときにできるのは、コインを捏造することではなく、過去の支払いを無かったことにすること。

ハッシュレートの現在の分布は↓のサイトなどで確認ができる。

https://blockchain.info/ja/pools

f:id:kusano_k:20180123054816p:plain:w480

このハッシュレートは、実際には過去の一定期間にブロックを採掘したマイニングプールの割合から計算しているのだと思う。 あるブロックがどこのマイニングプールから採掘されたのかが、なぜ分かるかというと、ブロックにマイニングプールの名前を刻んでいるから(なので、マイニングプールが強力なハッシュレートを使って悪さをしようとするならば、そもそもブロックに自分の名前を書かず、ハッシュレートをごまかすと思う)。

マイニングプールの名前は各ブロックに格納される一番最初の「取引」に書く。 この取引はマイニングの報酬が与えられる取引。

「取引」は入力と出力から構成される。 入力には、過去の取引のIDとScriptSig(基本的には過去にその取引でコインを受け取ったのが自分であることを示す電子署名)が並んでいる。 出力には、支払先のScriptPubkey(基本的には公開鍵)と金額が並んでいる。 マイニング報酬の取引の入力には「過去の取引」が存在しないので、IDには000…0を書き、ScriptSigの部分には好きなデータが書ける(はずだったけど、途中で最初にブロックの高さを書かないといけなくなった)。 ここにマイニングプールの名前が書かれている。例えば、505582番目のブロック最初の取引を見ると、/BTC.COM/ という文字列が含まれている。

暗号通貨のマイニングというとマイニングプールに繋いで採掘するのが普通だが、これでは最初の取引に自分の名前が刻めない。

NEETCOINという暗号通貨があるのを知った。 今からビットコインのブロックを採掘するのは無理だけど、新しい暗号通貨ならば採掘している人が少ないから何とかなりそう。

NEETCOINのハッシュアルゴリズムは、Litecoinや昔のMonacoinと同じScrypt。 ScryptはASICが出回っているので、CPUやGPUで太刀打ちするのが難しい。 以前に同僚からScryptに対応したASICを買い取っていた。

他で見ない形をしているけれど、Gridseed miniが2個。 元から付いているヒートシンクを外して、基盤を表と裏に貼り付け、別途ファンを付けたらしい。 1個で毎秒約250kハッシュ(khash/s)、2個で500 khash/s。 CPUだと4年前に3万5千円で買ったCore i7 4790 3.60GHzで80 khash/sくらい。 ただ、これでも最新の高いASICよりはだいぶ能力が劣るらしく、数日動かしても1個もブロックが掘れなかった。 26万円くらいのAntminer L3+は504 Mhash/sらしい。

こういう機械を持っている人の計算量(ハッシュレート)を買うことにした。 NiceHashが有名。 入金までしてみたものの、なかなか使い勝手が悪い。 NiceHashではハッシュレートを取りまとめて送ってくるので強力すぎる。 上限は設定できるけれど、最低でも0.01 Thash/s。 これは採掘をしていたときのネットワーク全体のハッシュレートよりも強い。 採掘できるコインが一定になるように難易度が調節されるので、難易度が上がって効率が悪くなる。 あと、他の購入者の設定金額との兼ね合いで、ハッシュレートが回ってきたり来なかったりする。 まあ、すでにネットワーク全体のハッシュレート高い暗号通貨を、後述のような試行錯誤をしたりすることなく採掘する分にはこれで困らないのだろう。

Mining Rig Rentalsならば、1人の相手からハッシュレートを買えるので、強力すぎないしハッシュレートも一定。

この手のハッシュレート購入サイトは、Stratumというプロトコルの接続先を指定してハッシュレートを買う。 手元で動かしている暗号通貨のウォレットとのプロトコル(Getwork)とは異なるので、変換する必要がある。

プロキシしてくれるプログラムがあるだろうとググってみると、stratum-mining-proxyというのが出てきたが、これはGetworkのマイニングツールをStratumのサーバーに接続するためのもので逆方向。 マイニングプールはまさにStratumをGetworkに変換しているので、大仰する気もするが、自分でマイニングプールを動かさないといけないらしい。 これがなかなか面倒だった。

https://github.com/Crypto-Expert/stratum-mining

このソフトを使う。 フォークが色々とあるけれど、結局本家のこれが一番新しいのではないかと思う。 ただし、ちょこちょこ修正が必要。

Wikiにセットアップ方法が書かれている。

まず、依存ソフトのstratumが、distribute_setupのエラーでビルドに失敗するので、別途pipでインストールしてsetup.pyを書き換える。 参考

stratum-mingも修正する

別にマイニングプールで人を集めるわけでも無いのでSQLiteで充分だけど、実装されていないところがあるので、MySQLをセットアップして使う。 DBの初期設定にはこのSQLを流し込む。 結局試していないが、もしかしたらDB無しでも動くかも。

設定ファイルに追加したり、変更したりした項目は下記の通り。

CONFIG_VERSION = None
CENTRAL_WALLET = '自分のNEETCOINのアドレス'
COINDAEMON_TRUSTED_HOST = '自分のウォレットのIPアドレス'
COINDAEMON_TRUSTED_PORT = 21010
COINDAEMON_TRUSTED_USER = 'user name'
COINDAEMON_TRUSTED_PASSWORD = 'password'
ALGO_NAME = 'ltc_scrypt'
CUSTOM_HEADER =  "000000800000000000000000000000000000000000000000000000000000000000000000000000000000000080020000"
CUSTOM_DIFF1 = 0x0000ffff00000000000000000000000000000000000000000000000000000000
COINDAEMON_REWARD = 'POS'
DB_MYSQL_PASS = 'pooldb'
USERS_AUTOADD = True
COINBASE_EXTRAS = 'この値がブロックに書かれる'
POOL_TARGET = 76294
VDIFF_MAX_TARGET = 76294
BOT_ENABLED = False

POOL_TARGETVDIFF_MAX_TARGETの値を大きくしてプールの難易度を上げておかないとMining Rig Rentalsでエラーになる。

0.5 LTC(1万円分くらい)で、約500 NEETCOINを採掘できた。 採掘している途中でNEETCOINを取り扱う取引所が出てきたからか、採掘する人が増えたので今はもっと効率が悪い。

残念ながら、NEETCOINのブロックエクスプローラにはマイニングプールの名前やScriptSigは出てこない。 例えば87190番目のブロックは私が採掘したので、ウォレットのデバッグウィンドウでgetblockbynumber 87190 trueと打ち込むと私のIDがcoinbaseに書かれているのが分かる。

"vin" : [
{
"coinbase" : "0396540104dea8635a08f8000001010000000b2f406b7573616e6f5f6b2f",
"sequence" : 0
}

コミックマーケットC93

コミックマーケットC93で↓の本を頒布した。

ZIP、完全に理解した

f:id:kusano_k:20180121012800j:plain:w320

COMIC ZINで委託販売をしている。

http://shop.comiczin.jp/products/detail.php?product_id=35822

次のために諸々メモ。

刷り部数

前回と同じく100冊刷ったが、12時ちょっと前に完売。 もっと刷るべきだった。 とはいえ、CTFよりさらにニッチだし、告知ツイートのRTやFavもそんなに多くはなかったので、これでも強気のつもりだったのだが……。 ただ、前回は誰かに頼まれて複数買っていく人がいたけど、今回はそういう人がいなかったし、CTFほど売れなかったとも思う。

決済手段

Squareに使える端末がスマホしかなかった。 リーダーを接続するときにちょっと時間が掛かるので、タブレットを持っていって繋ぎっぱなしにしたほうが良かったかもしれない。

ポスター

PO.SU.TAのProとminiを買った。 高いがこのコンパクトさはすごい。 ケースも厚手でサイズがピッタリなので良い感じ。

kinko'sで値段を確認せずにA2サイズのポスターを印刷したら4,000円かかった。 印刷前に「仕上がりはこんな感じになります」と一部分を試し刷りしてくれたり、やけに丁寧だと思ったら……。 印刷所で本と一緒に注文すると余白カット付きでも2,000円らしいので、次からはこっちにしよう。 後ろにポスタースタンドがある時点で邪魔になるので、サイズはA1で良さそう。

ビットコインの送金手数料を安くしすぎてしまった場合の対処法

ビットコインの送金手数料

私が「1 BTC(約100万円)を持っている」というのが実際にはどういうことかというと、「私が秘密鍵を知っている公開鍵Xに対しての1 BTCの送金がビットコインブロックチェーンに存在する」ということである。 私がここから、0.01 BTCをAさんに送金して、マイナーには0.0001 BTCの送金手数料を支払おうとしたとき、私の使っているクライアントは「公開鍵Xから0.01 BTCをAさんの公開鍵に、0.9899 BTCを私の別の公開鍵Yに送金する」という取引指示を公開鍵Xに対応する秘密鍵で署名してビットコインのネットワークに送信する。大量のビットコイン採掘用ASICを抱えるマイナーは、この取引をブロックに詰めて、ブロックのnonceの値を試行錯誤してブロックのハッシュ値をある閾値よりも小さくする。これが成功して新しいブロックをチェーンに繋げることができると、マイナーは採掘報酬の12.5 BTCと、私の取引の入力と出力の差である0.0001 BTC(と、ブロックに詰めたその他の取引の手数料)が手に入る。

マイナーは当然ビットコインが欲しいので、手数料が高い取引を優先してブロックに詰める。 早く承認してほしければそれだけ手数料を積む必要がある。 最近この手数料が高騰していて「1時間以内にブロックに詰めてほしいな」と思うと、1,000円くらい払う必要がある。 ビットコインなら銀行送金に比べて手数料が格安という話が昔はあったなぁ……。

別に急ぐ送金でもないしケチるかと思って手数料を数円にしてしまったら全くブロックに含められなくて困った。 昔ならこれでも待っていれば何とかなったけど、最近は送金が多すぎてブロックは常にいっぱいなのでこんな安い手数料が詰められることはもはやなさそう。

Replace-by-Fee

公開鍵Xからの送金がブロックに含められていないということは、公開鍵Xにはまだビットコインが残っているということで、ここから手数料を上乗せした別の取引を作るという手がまず思い浮かぶ。 が、これは上手く行かない。 ビットコインのネットワークは、すでにネットワーク上に存在する取引と衝突する取引は(その取引がブロックに含められる前でも)拒否する(という動作がリファレンス実装)。

これを認めてしまうと二重支払いが容易になって、ビットコインの使い勝手が悪くなるかららしい。 逆に、これが認められていないので、ビットコインを支払われた側は取引がネットワークに広まった時点である程度は安心できる。

最初の送金時に「後で上書きするかも」というフラグを付けるReplace-by-Fee(RBF)というプロトコルがあって、Electrumに実装されているらしい。

Opt-in Replace-by-Feeによるトランザクションの置換(BIP-125) - Develop with pleasure!

Electrum2.7系で実装されたビットコインの「opt-in RBF」とは何か - ビットコインの情報サイトの運営者ブログ

待つ

ネットワーク上に送信されて、ブロックに入れられていない取引は各ノードのmempoolに入っている。 Mempoolのサイズは有限なのでそのうち消えるはずで、理屈の上では消えた後ならば、改めて手数料を上乗せして送金できるはず。 ただ、ウォレットアプリが一度送信済みとマークした取引をネットワークから消えたのを検知して元に戻すという処理を持っているのか分からない。 どうなんだろう?

ちなみに、たとえ手数料が低くても古くなった取引は優先度を上げるという仕組みが昔はあったけど、無くなったらしい。

Transaction Accelerator

マイニングプールViaBTCが、指定した取引を優先してブロックに詰めるというサービスを(たぶんボランティアで)行っている。 ここに取引IDを入力すれば良い。 ただ、↑の取引で試したら「手数料安すぎ」と蹴られた。

Transaction Accelerator - ViaBTC

お釣りアドレスを使った新たな取引

上手くいった方法。

最初の説明の「0.9899 BTCを私の別の公開鍵Yに送信する」の公開鍵Yから、それなりの手数料を付けて送金する。 マイナーがこの取引のそれなりの手数料を手に入れるには、最初の手数料の安い取引も同時にブロックに含める必要がある。

Bitcoin Coreならば、設定→オプション→ウォレット→コインコントロール機能を有効化する で送金時にどのアドレスから送金するかを選択できるようになる。 「送り先」を自分のアドレスにして、このアドレスに入っている全額を「金額」に入力し、「送金額から手数料を差し引く」にチェックを入れて、適当な手数料を選べば良い。 マイナーはこの手数料を得るために、2個の取引をブロックに詰める必要があるので、普通に選ぶ手数料の2-3倍くらいにしておくと良さそう。

f:id:kusano_k:20171107012528p:plain

コインコントロール機能が無いウォレットならば、自分の持っている全ビットコインを自分に送金すれば、お釣りアドレスも使われるだろうか? 送金が承認されるまで全ビットコインが使えなくなるかもしれないけど。

たまたま承認されただけかもしれないので、同じように困っている人がいたらこれを試して、上手くいったかどうかとか、ブロックに含めたマイニングプールがどこかとかをコメント欄ででも教えてほしい。

源ノ角ゴシックや源ノ明朝をインストールした環境でmoraダウンローダーが起動しない問題

やっと原因が分かった。

源ノ角ゴシックや源ノ明朝のSuper OTC版(言語・ウエイト全部入り)のフォント名が長すぎて、.NET Frameworkがバグっていた。 アンインストールして、Region-specific Subset OTFを入れ直しましょう。

f:id:kusano_k:20170917154922p:plain:w480

moraダウンローダ

f:id:kusano_k:20170917200512p:plain:w480

moraという音楽配信サイトがある。 クールごとにアニソンをまとめたページがあって便利。 DRMフリー。 過去にはダウンロードは10回までという制限があったが、(明記はされていないものの)今はこの制限も無くなったっぽい。

専用のmoraダウンローダーがある。 これを使うと、複数の曲をまとめてダウンロードすることができ、ファイル名を変えてフォルダ分けして保存してくれる。

今期のアニソンを買ってダウンロードしようとしたら、「moraダウンローダーは動作を停止しました」と出てきて起動しなくて困った。

f:id:kusano_k:20170917174947p:plain

源ノ角ゴシックと源ノ明朝

「げんのかくごしっく」「げんのみんちょう」と読む。Adobeが配布しているOSSフォント。

フリーなのにウェイトが多くて楽しい。 1個のフォントをソフトウェア処理で太字にするのは素人で、プロは最初から太さを変えて作られたフォントを使うらしい。

f:id:kusano_k:20170917195410p:plain

ダウンロードページ(ゴシック明朝)に行くと、種類が色々あってどれをダウンロードして良いのか迷う。 最近のOSにしか対応していないが、Super OTCというのをダウンロードすれば、1ファイルで言語もウェイトも全て揃う。

これが間違いの元だった……。 フォントをインストールすると、レジストリHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fontsに次の2個の値が追加される。長さはどちらも259文字。

  • 源ノ角ゴシック ExtraLight & Source Han Sans K ExtraLight & Source Han Sans SC ExtraLight & Source Han Sans TC ExtraLight & 源ノ角ゴシック Light & Source Han Sans K Light & Source Han Sans SC Light & Source Han Sans TC Light & 源ノ角ゴシック Normal & Source Han Sans K Normal & Sou
  • 源ノ明朝 ExtraLight & Source Han Serif K ExtraLight & Source Han Serif SC ExtraLight & Source Han Serif TC ExtraLight & 源ノ明朝 Light & Source Han Serif K Light & Source Han Serif SC Light & Source Han Serif TC Light & 源ノ明朝 & Source Han Serif K & Source Han Serif SC

中身を見ると、倍くらいの長さのフォント名を書きたがっているように見えるので、どこかで切り捨てられているらしい。

Adobeがこのフォント名にしているならば文句を言いたいけれど、セットになったフォントの名前を&で連結してレジストリの値名にしているのはWindowsだろうか?

調べ方

moraダウンローダーは黙って落ちるので、何も情報は得られない。 .NETだから普通にデバッグはできないし、どうするのだろうと思ったけど、便利なソフトがあった。

dnSpy

アセンブル、逆コンパイル等のソースコード解析作業ではなく、moraダウンローダーがスルーしている例外を調べる目的で使う。 分かりにくいけど「build passing」のバッジからビルド済みのバイナリがダウンロードできる。

このソフト上で実行すると例外が発生したときに、例外の中身が見られる。 $exception変数のInnerExceptionを順番に見ていくと大元の原因が分かる。

f:id:kusano_k:20170917190200p:plain:w480

レジストリが何とかで落ちているので、あとはSysinternals SuiteProcess Monitorを使うと、レジストリにどのような操作をしているかが分かる。 長さ512(たぶんバイトなので256文字)のバッファで読もうとしてエラーが返っていた。 惜しい。 あと4文字長ければ……。

RegEnumValueERROR_MORE_DATAを返しているだけなので、バッファーオーバーフローなどではなく、細工されたフォントをインストールしてmoraダウンローダーを起動するとシステムを乗っ取られるとかはない。

詳細

https://github.com/kusano/mora_not_run

.NET Frameworkの3.5以下の版には、RegistryKey.GetValueが256文字以上の値の名前を列挙できないというバグがあるらしい。 そんな制約は書いてないぞ。

試しに.NET Framework 2.0でウィンドウを作ってみたけれど、フォントの一覧は読みにいかなかったので、何がトリガーなのか分からない。

moraダウンローダーは.NET Frameworkを使っているだけなので、対応するなら.NET Frameworkのバージョンを上げるしかなさそう。

Registry Element Size Limits (Windows)

MSDNを見てみると、値の名前の最大長は16,383文字。 過去には(ANSIだけど)260文字という制約があったこともあるらしい。 フォント名が259文字に切り詰められていたのは、260文字までしか対応していない環境やソフトを意識してのことだろうか。 比べてみると最大長が256文字のRegistryKey.GetValueは雑。

ちなみに、Windowsレジストリエディタは260文字以上の長い値名を表示することはできたけれど、長い値名を設定しようとすると259文字に切り詰められた。

おまけ

moraダウンローダー以外のアプリが28e89a9f-e67d-3028-aa1b-e5ebcde6f3c8というMutexを作成していると同様に落ちる。 ↑のフォントのレジストリを列挙するプログラムでは、ついでにこのMutexを作成しているので、確認ができる。 たぶん、同時起動抑制とかに使っているのだと思う。 この文字列がmoraダウンローダーのGUIDならば何の問題も無いのだけど、これは.NET Framework 2.0のSystem.Reflection.AssemblyのGUID。 何か勘違いをしている?

あと、moraダウンローダーを起動して、最初の1曲目のダウンロードが「エラーコード:21003」で失敗するという問題がある。

サポートに訊いたら、「プロキシ設定やネットワーク通信状況に起因した問題である可能性」と言われたけど、最初の1曲目は常に失敗するし、Twitterを検索しても似たようなことを言っている人がいるし、ネットワークの問題などではないのでは。ついでに直してほしい。