本文へ移動
Hikari 仕様

std:crypto/random — 暗号用乱数

標準ライブラリモジュール (../index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../../language-spec.md を参照。

import crand := "std:crypto/random"ホストのエントロピー源から予測不能なバイト列を取る slot 1 つを持つ namespace object を crand に束縛する。std:crypto/randomimport 自体が「このコードはエントロピーを引く」ことの能力宣言になる (../index.md)。

std:crypto (../crypto.md) とは別のモジュールである。あちらは入力から出力が一意に決まる純粋なハッシュと HMAC を持つのに対し、こちらは呼ぶたびに違う値を返す。純粋であることは std:crypto の契約なので、そこへエントロピーの口を混ぜない。鍵とトークンを作るのはこちら、それを署名するのがあちらである。

std:random (../random.md) とも別である。あちらは統計的な擬似乱数で、再現できる列 (seed) を主な用途に持ち、ジェネレーター object に可変状態を閉じ込める。こちらは再現できず、状態を持たず、値を直接返す。鍵・トークン・パスワードの生成には必ずこちらを使うstd:random は統計的性質しか主張しない。

std:fs のような外部リソース I/O ではなく、std:math と同じく同期で値を直接返す (Future を返さない)。エントロピーの取得はホストの 1 回の呼び出しで完結し、名前で指す外部リソースを開くわけでもないので、! を強いる形にしていない。

import crand := "std:crypto/random"

key := crand.bytes(32).unwrap!        #> <32 bytes> 呼ぶたびに異なる
token := crand.bytes(16).unwrap!      #> <16 bytes>

以下では import crand := "std:crypto/random" で束縛したものとして記す。

1. slot 一覧

名前 効果 意味
bytes crand.bytes(n) Rand エントロピー由来の n バイト → Result(Bytes) (§2)

2. crand.bytes

  • 引数 n は Int。0 以上でなければ呼び出し位置で Error (バグ層。負の長さは要求として意味を持たない)。非 Int も Error
  • n0 なら Ok の空 Bytes。エントロピーは引かない。
  • 上限は設けない。大きな n はホストの読み取りが分割されうるが、要求した長さちょうどを返すか Err を返すかのどちらかである — 短く返ることは無い。ただし、実行環境が長さとして表現できる範囲を超える n はバイト数の要求として成り立たないので、その nErr ではなく呼び出し位置の Error である。32 ビットの環境 (wasm 等) ではこの境界が Int の表現できる範囲よりも手前に来る。
  • 成功時は Ok(bs)bs.length!n に等しい。失敗時は Err({kind, message}) (§4)。

返り値は Result であって生の Bytes ではない。 エントロピー源は「無い環境が実在する」もので (§3)、unwrap! を強いる形にしておかないと、持っていない能力が黙って弱い値に化ける。std:crypto のハッシュが Result を返さないのは、あちらが失敗しうる外界に触れないためである。

2.1 品質の主張

予測不能性を主張する。 ホストが提供する暗号論的擬似乱数生成器 (§3) をそのまま通し、この処理系の側で伸長・混合・キャッシュをしない。過去の出力から次を予測できないこと、内部状態が漏れても過去の出力を再構成できないことは、ホストの保証に従う。

再現する道を持たない。 種を与える口は設けない。試験のために固定値が要るなら、値を作る関数の側を引数で受ける形に書き換えること (pkg:recordrun が時刻を注入するのと同じ形)。種を与えられる暗号乱数は、その種が漏れた瞬間にすべての鍵が漏れる。

3. 対応プラットフォーム

ホストのエントロピー源は次を使う。いずれもブロックしない (プールの初期化待ちを除く)。

プラットフォーム
Linux getrandom(2)
macOS getentropy(2)

どちらも同じ 1 本の系統呼び出しの層で埋まり、追加の設定やインストールを要しない。

これ以外のプラットフォームでは提供しないn0 より大きければ bytesErr(unavailable) で返る (n0 なら §2 のとおりエントロピーを引かずに Ok である)。対象は wasm (Playground と REPL の面) と Windows である。したがって wasm では、これに依るコード (署名付きセッション等) は動かない。import は解決するが呼ぶと失敗する形は std:db/sqlite (../db/sqlite.md §10) と同じである。

Windows を挙げていないのは、この処理系が Windows を対象に持たないからであるhikari build の対応ターゲットはホスト 1 つだけで (../../build.md)、ホスト能力の実装は Unix の系統呼び出しの上に立っている。ここだけ Windows の源を約束しても、それを確かめる道が無い。

4. エラーモデル

失敗時の err は回復可能エラーオブジェクト {kind, message} (../../language-spec.md §16)。正規 kind (閉じた一覧):

kind 意味
unavailable このプラットフォームにエントロピー源が無い (wasm)
io_error 源はあるが読めなかった (ファイル記述子の枯渇・プールの初期化待ちの中断ほか)

messagecrypto/random.bytes: ... のように関数名を含む人間可読文字列。分岐は kind で行い、message の文字列マッチに依存しないこと。

引数の型・範囲違反 (非 Int・負の n) は Error (バグ層、../../language-spec.md §16.2) であり Err ではない。

5. 将来枠

  • uint64() / int(lo, hi) — 偏りの無い範囲付き整数。今は bytes から呼び手が組む。剰余の偏りを避ける書き方 (棄却サンプリング) を std が持つ値打ちはあるが、bytes の上に純 Hikari で書けるので急がない
  • fill(buf) — 呼び手が確保した可変なバイト列へ直に書き込む口。std:arrayArray は要素がバイトではなく Hikari の値を持つハンドルなので使えない (../array.md §1。32 バイトの鍵のために 32 個の箱入り値を確保してから bytes() でまとめるのでは、割り当ては減るどころか増える)。std に可変なバイト列の型が入れば、そちらへ直に埋める口を足せる — 確保する主体が呼び手へ移るだけで、bytes(n) はその上に立つ便宜口にできる。それでも上限が無くなるわけではない — 確保する主体が変わるだけである