Nim (4)

頼んでいた『暗号解読』が入荷しましたから、一週間以内に借りに来てくださいってTELが有った。 散歩がてら、畦道、間道を抜けて、図書館まで取りに行った。往復1時間とは、丁度良い距離だな。

年度の切り替わり時期で、図書館の決算報告書が掲示板に出てた。図書館は、皆様からの 税金のうち2000円づつを持って運営されてます、ですって。

そうか、年会費2000円の強制永久会員か。2000円じゃ、ハードカバー本1冊、もしくは文庫本3冊 ぐらいしか買えないな。オイラーは、読み放題の特典を生かして、月に10冊以上、20000円 相当ぐらいを読破してるから、図書館のヘビーユーザーだな。

何時行ってもいる、新聞読み比べ老人達とか、おかあさんと何時も一緒って母子セットもいるから、 図書館の使い方は多用ですな。

最近は、 3月13日公開の映画『イミテーション・ゲーム』:世界最初のコンピューター科学者がナチスを打ち負かす物語 なんてのも出てて、チューリングがブームですな。勿論、暗号解読本にも出てきます。

ドイツが作った、暗号・復号用タイプライターである、エニグマの話が面白い。この謎めいた 機械の動作が良く分かりましたよ。

暗号の原理には、大きく分けて2種類ある。平文の1字1字を、違う文字に置き換えていく方式。 文字を置き換えるので換字式暗号と言う。前回やった、ROT13なんてのは、典型的な例になる。

もう一つの種類は、転置式暗号と呼ばれるものだ。平文の文字の配置を入れ替える、トランプで シャッフルするのと同じ。完全にシャッフルしちゃうと、元に戻せなくなるので、ある規則で 文字を並べ替える。一番簡単なのは、2文字づつに分解して、そのペアの中で左右の文字を 入れ替える方法。rubyってのを転置するなら、ruとbyに分けてから入れ替える。urybになる。 他にも、原稿用紙に縦書きにしたのを、横書きで読むとか。。。

エニグマは、前者の換字式暗号を使ってる。但し、普通に文字を置き換える方式だと、平文の 持つ言語特徴を勘案して(文字の使われ方の頻度から、元の文字を推測するとか)、暗号を破る事が 出来るので、特徴を消し去るような仕掛けが何重にも施されている。

換字式暗号の破り方は色々と解説されてるけど、転置式の破り方には全く触れられていない。 素人さんには難しすぎて説明出来ないのかな。どうしても知りたかったら、数学を勉強してねって事か。

それから、もう一つ疑問が有って、平文は勿論人間が読めるものだけど、例えば平文をコンピュータで 圧縮したものを暗号化する手がある。これだと、正しく解読しても、圧縮した物が出てくるはず。 そもそも、圧縮の時点で、平文の言語的特性が失われているので、解読出来るのか? (圧縮されてるのがバレないように、magicコマンドの目の付け所を調べておく等の工夫が必要)

こういう疑問は、誰に聞けばいいの? 暗号技術入門のあの人?

プチ不満は有ったけど、 暗号方式を開発する人と破る人のせめぎ合いを楽しめましたよ。そう言えば、こういう知恵比べ。おいらも昔、やったね。 ゲームのプロテクト外し。

別に、プロテクトを外して、ゲームを無料でやりたいとか、金儲けをしたいとか言う不純な 動機からではない。

プロテクトがかかったゲームが有る。どうやってるの? 外してみたいな。 小供が、目覚まし時計ってどうなってるの? 分解して調べてみようと思う気持ちですよ。 世界の有名な学者だって、同じ事をやってるよ。いい大人ですけどね。

スイスとフランスとドイツの国境をまたがるあたりの地下数100メーターの所に作られた、秘密基地に結集してね。 日夜、原子に鉄砲玉を打ち込んで分解し、その破片を調べて、どうなってるか考えてる。

何と純粋な人達でしょう。チューリングさんも同じですね。ただ、彼らの場合、暗号破りと言う 国家機密に関わっていたため、厳重に秘されていましたけどね。

RSA

上記の本に有名な公開鍵暗号が(やはり)話題として取り上げられている。巨大な素数の積を 、素数に分解するのは大変だよって事に基礎を置いている。

どれぐらい大変なの? 素因数分解。

素因数分解問題調査研究報告書 とか、 素因数分解とかエラトステネスの篩(ふるい)とかのメモ に、調べた人がおられた。

自分のマシンでも追試してみるかな。書かれたコードがPythonだけど、オイラーにはNimの ごとく見えるな。ちょこざいと修正するだけで動きそう。だったら、気を利かせた人が、python2nimとかの、 トランスレータを書かないかねぇ。そうすれば、実行スピードはC語と同等なんだから、流行ると 思うよ。

このRSAは、素数夜曲な本にも取り上げられてて、イギリスの情報部が、R・S・Aな人達よりも、 3年前に発明してたってなってたな。数学なんて世の中の役にたつの?って疑問に対する、 りっぱな回答ですな。

第一次戦争は化学者が活躍した(毒ガス兵器で)。第二次戦争は物理学者が活躍(核兵器で)。 そして、大惨事戦争が有るとすれば、数学者が活躍するはず。(暗号とかの情報戦争用兵器)

アメリカは熱心に数学者をアメリカ国家安全保障局(NSA)に囲い込んで、いろいろやってるらしい。 NSAってのは、NSAはあまりに全貌が不明瞭なため、その略称は「Never Say Anything(何も喋るな)」 「No Such Agency(そんな部署はない)」の略だと揶揄される事も有る。

素因数分解が難しいなら、新しいパラダイムに基づくコンピュータを開発しちゃえ。そう量子 コンピュータが出来れば、一瞬にして解けちゃうぞ。RSAは役に立たなくなる。

そんな事もあろうかと、量子暗号が研究されてる。こちらは、盗聴してる事が判定出来るって代物。 盗聴出来なければ、極端な話、平文で相手に送っても大丈夫。

但し、Never Say Anything なので、どこまで実現されてるかは不明。機密情報暴露で有名に なったのは、元CIAのエージェントであるスノーデン氏だったけど、世界平和の実現の為、 NSA版の第二のスノーデン氏が現れることを期待。

解読難易度が高い暗号の一つにストリーム暗号ってのがある。平文を鍵とEXORしてくやつ。 鍵が常に変わっていけば、解読出来ない。これの応用先として、WiFiが有る。

常に変わる鍵は乱数生成器によって得られる。乱数の周期が長ければ、事実上解読は出来ない。 暗号強度は、ひとえに乱数発生器の品質いかかっている。 円周率は、乱数発生器としては、 優秀らしいぞ。

ああ、こんなのが有った。

第2回 暗号解読とストリーム暗号

ストリーム暗号の現状と課題

Haskell memo

Haskellのパッケージ管理について調べてみた

Haskellでデータ処理がしたい!

Haskell Tips (文字列編)

read_csv

前回からの続き。今度はCSVファイルのデータを読み込む所。nimのモジュールを探ると 旨い具合に提供されてるので、そのまま利用させてもらいました。

import os, parsecsv, streams
import strutils

const SEED = "./z.csv"
type
    ds = array[4, int]

proc pp(things: seq[ds]) =
    for d in things:
        echo(d[0]," ",d[1]," ",d[2]," ",d[3])

proc read_csv() : seq[ds] =
    var s = newFileStream(SEED, fmRead)
    var x: CsvParser
    var rv = newSeq[ds](0)
    open(x, s, SEED)
    while readRow(x):
        var al : ds
        var i : int =0
        for  val  in items(x.row):
            al[i] = parseInt(val)
            i += 1
        rv.add(al)
    close(x)
    return rv

var bld = read_csv()
pp(bld)

nimの場合も勿論、サイズ成長可能な配列が用意されてます。型名は seqです。シーケンスの 略かな。で、この中には、サイズ固定の配列が追加されます。配列の型は、typeを使って宣言しときました。 これで、多少入力が楽になりますね。

配列をアクセスしるインデックス変数は、自前でプラス1してるけど、nimの流儀では、inc(i)と 書くようです。果たして違いは何処に?

浮動小数点の印字

nimには、いわゆるフォーマットプリントが無いんだよな。だから、初回でやったように、勇士さんが 拡張モジュールを提供してる。

それは有り難いんだけど、あちこちにソースを持って行ってコンパイルしようとすると、面倒になる。 なるべく、標準品だけで済ませたいのが本音。

今回は、フォーマットは固定で良いので、頑張って書いてみる。%6.1f で良いのさ。正式な やつだと、少数点2桁目を四捨五入して1桁目に反映してるけど、面倒には目を瞑る。

10倍してそれを整数に変換。10で割った商と余りに分けて、ドットで結合すればいいな。 桁を揃えるための空白の挿入が面倒。何か補助が無いかと、strutilisをまさぐる。

repeatChrなんてのが見つかった。これを使えば良いな。と、思って周辺を見渡したら、 もっと楽を出来るものが見つかった。

proc f2s(f: float) : string = # Same as sprintf("6.1f", f)
    let f10 = int(f * 10)
    let u = f10 div 10
    let d = f10 mod 10
    return align($u & "." & $d, 6)

alignがそれ。特に指定しないと、空白を補ってくれる。便利。

で、ものはついでと思って、このモジュールをぱらぱらしてたら、結構充実してましたよ。 上の関数を書いちゃった後だけど、

proc c_sprintf(buf, frmt: cstring) {.header: "<stdio.h>", importc: "sprintf",
                                     varargs, noSideEffect.}

type
  FloatFormatMode* = enum ## the different modes of floating point formating
    ffDefault,         ## use the shorter floating point notation
    ffDecimal,         ## use decimal floating point notation
    ffScientific       ## use scientific notation (using ``e`` character)

{.deprecated: [TFloatFormat: FloatFormatMode].}

proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
                         precision: range[0..32] = 16): string {.
                         noSideEffect, rtl, extern: "nsu$1".} =
  ## Converts a floating point value `f` to a string.
   :

実体は、C側のsprintfに丸投げしてるんですけど、良く使いそうなやつを用意してるNim開発者に 拍手を送りますよ。Webとかをやる時、このあたりが充実してないと、顧客に逃げられますからね。

ちょいと疑問が出てきたので、実験。疑問とは、importするとき、モジュール全部を取り込むのと、 指定したものだけにするのとでは、出来上がるオブジェクトサイズに差が出るのってやつ。

[sakae@fedora t]$ cat size.nim
import strutils
let f = 12.345678
echo formatFloat(f, format = ffDecimal, precision =3)
[sakae@fedora t]$ nimhs size.nim
12.346

strutils.cは、行数が69行だった。もう結論が出ちゃったけど、一応確認。

from strutils import formatFloat, FloatFormatMode

先頭行を変更したもので確認したら、実行サイズ、C語のソースサイズも同一だった。 Nimのソース上でみんな持って来る指定をしておいても大丈夫だよ。関係者しかC語に変換されないからね。

たとえ関数名が同一でも、アリティが異なれば、違う関数と 看做されるしね。衝突する事はめったに無いでしょう。

差は、どこのモジュール出身かプログラミングしてる人に明確になる事ぐらいかなあ。 python陣営では、使うものだけインポートしろってのが推奨されてる。あちらは、あちらの 事情が有るんでしょうな。

無理して特定なものだけにすると、足をすくわれるから注意。 実は、最初、FloatFormatModeを持ってくるのを忘れ、ffDecimal なんて知らないって怒られたのさ。

完成したぞ

### blood data stat
import os, parsecsv, streams, sequtils
import strutils, osproc
from algorithm import reversed
from posix import exitnow

const SEED = "./current.csv"
type
    Opt = object of RootObj
        ap: int # 1: am, 2: pm
        tl: int # Number of data
        ss: int # stat
        pp: int # print
        lg: int # line graph
        er: int # error detect
    ds = array[4, int]

proc getopt() : Opt =
    var args  = newSeq[string](0)
    for i in  0 .. paramCount(): args.add(paramStr(i))
    args[0] = "fin"
    result = Opt(ap:0, tl:0, ss:0, pp:0, lg:0, er:0)
    var av = 0
    for s in args.reversed():
        case s:
            of "fin":
                if paramCount() == 0: result.er = 1
            of "-am": result.ap = 1
            of "-pm": result.ap = 2
            of "-ss": result.ss = 1
            of "-pp": result.pp = 1
            of "-lg": result.lg = 1
            of "-tl": result.tl = av
            else:                        # Must be number
                try: av = parseInt(s)
                except: result.er = 2

proc pp(bld: seq[ds], cw: int) : seq[ds] =
    if cw != 0:
        for d in bld: echo(d[0]," ",d[1]," ",d[2]," ",d[3])
    return bld
    
proc read_csv(seed: string) : seq[ds] =
    var s = newFileStream(seed, fmRead)
    var x: CsvParser
    result = newSeq[ds](0)
    open(x, s, seed)
    while readRow(x):
        var al : ds 
        var i : int =0
        for  val  in items(x.row):
            al[i] = parseInt(val)
            i += 1
        result.add(al)
    close(x)

proc ampm(bld: seq[ds], cw: int) : seq[ds] =
    result = case cw
        of 1: filter(bld, proc(e: ds): bool = (e[0] mod 100) < 12)
        of 2: filter(bld, proc(e: ds): bool = (e[0] mod 100) >= 12)
        else: bld

proc tl(bld: seq[ds], cw: int) : seq[ds] =
    if cw > len(bld):
        echo("Req size is too big, so can't do that")
        exitnow(3)
    result = newSeq[ds](0)
    if cw > 0:
        for i in bld.len - cw .. bld.len -1 : result.add(bld[i])
    else: result = bld

proc w(bld: seq[ds], hlp: int) : seq[int] =
    result = newSeq[int](0)
    for e in bld: result.add(e[hlp])
    
proc max(ar: seq[int]) : float =
    var rv = low(int)
    for v in ar:
        if v > rv: rv = v
    return rv.float

proc mean(ar: seq[int]) : float =
    var sum = 0
    for v in ar: sum += v
    return sum / ar.len

proc f2s(f: float) : string = # Same as sprintf("%6.1f", f)
    let f10 = int(f * 10)
    let u = f10 div 10
    let d = f10 mod 10
    return align($u & "." & $d, 6) 
        
proc ssf(bld: seq[ds], fn: proc(x: seq[int]):float) : string =
    result = ""
    for hlp in 1 .. 3: result.add( f2s(fn(w(bld, hlp))) )
    
proc ss(bld: seq[ds], cw: int) : seq[ds] =
    if cw != 0:
        echo("size: " & $len(bld))
        echo("max: " & ssf(bld, max))
        echo("mean:" & ssf(bld, mean))
    return bld

template mk4gp(gd: string, bld: seq[ds], hlp: int) =
    var i =0
    for e in bld:
        gd.add($i & " " & $e[hlp] & "\n")
        inc(i)
    
proc lg(bld: seq[ds], cw: int) :seq[ds] =
    if cw != 0:
        var gd = "plot '-' w l\n"
        mk4gp(gd, bld, 1)
        gd.add(" \n")
        mk4gp(gd, bld, 2)
        gd.add("end\n")
        let pid = startCmd("gnuplot -p", {poStdErrToStdOut, poUsePath})
        let inp  = inputStream(pid)
        inp.write(gd)
        inp.close()
        close(pid)
    return bld

## main #################
let f = getopt()
if f.er != 0:
    echo("Can not do")
    exitnow(f.er)
var bld = read_csv(SEED)
discard bld.ampm(f.ap).tl(f.tl).pp(f.pp).ss(f.ss).lg(f.lg)

ampmって関数は、測定データから、午前・午後のデータを抽出するものだ。ifの結果は 変数に代入出来る事は知ってたけど、caseも同様なのね。 そして、filterなんてのが有ったので、使ってみた。名無しのprocで、抽出条件を決めている。 関数型っぽく書けて、嬉しいな。

templateってのが使えるそうだったので、無理して使ってみました。関数と同じ感覚で 使えます。けど、関数にしちゃうと呼び出しのオーバーヘッドが課されるに対し、 templateは、インライン展開なので、オーバーヘッドが有りません。ここら辺が、 使い所になるのかなあ。

エラーを検出してプログラムを終了させるには、posixに有るexitnowってのを使うみたい。 posixって事で、Windowsで普通に動くか危惧したけど、mingwでは普通に動きました。

それに対し、bcc32のコンパイラーをチョイスすると、タイムゾーンのインクルードが無いとか 言われて、コンパイル失敗。ちなみに、上記の完成品は、

[sakae@fedora bld]$ wc bld.nim
 131  537 3600 bld.nim
[sakae@fedora bld]$ wc nimcache/*.c
   229    602   5394 nimcache/algorithm.c
  1369   3731  35072 nimcache/bld.c
    14     61    508 nimcache/cpuinfo.c
    14     61    504 nimcache/hashes.c
   299    933   7802 nimcache/lexbase.c
    14     61    500 nimcache/linux.c
   452   1211  11086 nimcache/os.c
  1029   2828  26851 nimcache/osproc.c
   697   1971  17995 nimcache/parsecsv.c
   388   1080   8796 nimcache/parseutils.c
    43    136   1052 nimcache/posix.c
    97    288   2747 nimcache/sequtils.c
   496   1452  13745 nimcache/streams.c
    74    206   1765 nimcache/strtabs.c
   410   1097   9403 nimcache/strutils.c
  4413  12603 107299 nimcache/system.c
    14     61    500 nimcache/times.c
 10052  28382 251019 合計

こんな具合に、nimの力によって展開されてます。nimは高級言語なんだなあ。

bcc32との連携

上でbccと連携するとコンパイル失敗と書いた。コンパイル時に、-d;releaseを付けてあげると、 余計なものが付属しなくなったせいか少し先まで進むようになった。

でも、posixがある限りunix寄りなんだよな。と思っていたら、quitなんていう汎用的な やつを見つけたぞ。

    quit("Commad args error, Can not do...", f.er)

こんな風に軽い乗りで使える。このために特別なものをimportする必要無し。libの下を 覗いておくと思わぬ掘り出し物に出会うな。改めてソース嫁を実感しました。

気をよくしたオイラーは、emacs上からも使えるようにした。が、簡単なハロワの結果が 思わぬ所に表示されちゃう。

Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
c:\homes\nimdev\nimcache\tt.c:
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
c:\homes\nimdev\nimcache\system.c:
Borland C++ 5.5.1 for Win32 Copyright (c) 1993, 2000 Borland
Turbo Incremental Link 5.00 Copyright (c) 1997, 2000 Borland
hello world
config\nim.cfg(45, 2) Hint: added path: 'C:\Users\sakae\.babel\pkgs\' [Path]
config\nim.cfg(46, 2) Hint: added path: 'C:\Users\sakae\.nimble\pkgs\' [Path]
Hint: used config file 'c:\app\nim\config\nim.cfg' [Conf]
Hint: system [Processing]
Hint: tt [Processing]
[Linking]
Hint: operation successful (8912 lines compiled; 1.014 sec total; 6.666MB) [SuccessX]
c:\homes\nimdev\tt.exe 

Process nim-run finished

コンパイルの途中ですが、先に実行結果をお知らせ致します状態。bcc側の実行が速すぎて、 nim側で出しているメッセージの処理が追いついていないっぽい。

ならば、邪道だけど、ハロワアプリの中で処理を遅延させれば良い? osモジュールの 中に sleep(milsecs: int) なんてのが有ったんで入れてみたけど、今度はアプリが遅延 され、実行終了後に、nimの処理結果が表示される始末。なかなかむずい。

でも、よく考えたら、出来上がったアプリの実行後に出て来るやつは、nimからの、注意事項だった。 nimhsを忠実に真似て、 --verbosity:0 を入れて、抑止したら綺麗になった。やれやれ。