musubi

THEORY · 関係性暗号の理論

関係そのものに
意味を委ねる。

このページは musubi の中核理論——関係性暗号 (relational cipher)——を、論文に近い粒度で書き下ろしたものです。 概念の動機から形式定義、アルゴリズム、具体例、脅威モデル、未解決問題までを 一気通貫で扱います。

§01 · MOTIVATION

なぜ関係性暗号か

古典暗号の世界には、おおまかに二つの系譜があります。

  • 置換 (substitution) ── 各文字を別の文字に置き換える。シーザー暗号、単アルファベット換字、 ヴィジュネル暗号、エニグマ。
  • 転置 (transposition) ── 文字の順序を入れ替える。スキュタレー、二段階転置、 グリル暗号。

どちらも「平文の文字を、形を変えて暗号文に書く」という前提を共有しています。 関係性暗号はその前提を捨てます。 平文の文字は、暗号文には一文字しか書かれません(アンカーと呼ぶ)。残りはすべて、文字同士の関係として記述されます。

文字を運ぶのではなく、文字のを運ぶ。

これは現代暗号の理論強度を狙ったものではありません。 古典暗号の遊び場の中に、まだ誰も組んでいない構造を一つ立ち上げてみる、 そういうロマン駆動の試みです。

§02 · FORMAL MODEL

形式モデル

以下の記号を用います。

Σ
有限・空でない文字集合(アルファベット)。
|Σ|
Σ の要素数。default-v1 では |Σ| = 175。
π
Σ 上の全単射 {0, …, |Σ|−1} → Σ。すなわち
π⁻¹(c)
c ∈ Σ の ランク(鍵による位置)。
m = m₀ m₁ … mₙ₋₁
平文。各 mᵢ ∈ Σ。
n
平文長。
a
アンカー位置。0 ≤ a < n。
rᵢ
位置 i の 参照位置(i ≠ a)。
idxᵢ
ランク π⁻¹(mᵢ)。

以降のすべての算術は mod |Σ| で行われます。 つまり鍵で並べ替えられた円環の上で文字を操作する、というのが関係性暗号の世界観です。

§03 · RELATIONS

三種類の関係

関係 (relation) は、ある位置 i の文字を、別の位置 rᵢ の文字から導出するための小さな指示書です。 v1 では次の 3 種類だけを定義します。

Same

同じ
idxᵢ = idx_{rᵢ}

参照先と完全に同じ文字。連続する同一文字が暗号文の頻度として漏れないように、専用の関係として用意している。

Shift δ

ずらし
idxᵢ = (idx_{rᵢ} + δ)  mod  |Σ|

鍵での位置を δ だけずらした文字。δ は ±⌊|Σ|/2⌋ の範囲に正規化(最短の経路を取る)。default-v1 では δ ∈ [−87, 87]。

Mirror

反転
idxᵢ = (− idx_{rᵢ})  mod  |Σ|

ランクを 0 を中心に反転した位置の文字。π⁻¹(mᵢ) + π⁻¹(m_{rᵢ}) ≡ 0 (mod |Σ|) を満たす対をひとまとめで表す。

これら 3 種は形式上は SameMirror Shift の特殊例として表現できますが、 暗号文の構造的多様性とパターン解析耐性のために独立した kind として設けています。 たとえば回文「しんぶんし」は Mirror を多用すると暗号文上の構造が単調にならず、 解析者の手掛かりを減らせます。

§04 · ENCRYPT

暗号化アルゴリズム

正準エンコーダ(v1)は、 アンカーから外側に向かって 1 ステップずつ参照位置を割り当てます。 アルゴリズムは次の通り:

encrypt(m, π, a):
  n ← |m|
  各 mᵢ ∈ Σ を検証
  relations ← [None]·n
  for i = a−1, a−2, …, 0:
    relations[i] ← chooseRelation(mᵢ, m_{i+1}, i+1)
  for i = a+1, a+2, …, n−1:
    relations[i] ← chooseRelation(mᵢ, m_{i−1}, i−1)
  return Ciphertext(version=1, alphabet=π.id,
                    length=n, anchor=(a, m_a),
                    relations)

chooseRelation(c, ref_c, ref_idx):
  if c == ref_c:                  return Same{ref_idx}
  if (π⁻¹(c)+π⁻¹(ref_c)) ≡ 0:    return Mirror{ref_idx}
  δ ← (π⁻¹(c) − π⁻¹(ref_c)) mod |Σ|
  δ ← if δ > |Σ|/2 then δ−|Σ| else δ
  return Shift{ref_idx, δ}

参照位置の選び方は正準的に「アンカーに 1 歩近い隣接位置」で固定されます。 結果として、関係グラフはアンカーを中心とする両側のパスに揃います。 これは復号がもっとも単純で、暗号文の構造を読みやすい形に正規化するためです。

v0.2 ではこの選び方を緩和した チェーン (chain) エンコーダを導入します(§08)。

§05 · DECRYPT

復号アルゴリズム

復号はアンカーから出発するトポロジカルソートです。 参照グラフがアンカーを根とする有向非巡回であれば、 入口がいくつあっても線形時間で全位置を解決できます。

decrypt(C, π):
  検証: C.version, C.alphabet, length一致, anchor 整合
  chars ← [None]·n
  chars[C.anchor.position] ← Some(C.anchor.character)
  while chars に None が残る:
    progress ← false
    for i = 0..n:
      if chars[i] = None and chars[r_i] ≠ None:
        chars[i] ← applyRelation(relations[i], chars[r_i])
        progress ← true
    if not progress:
      reject as malformed (cycle / unreachable)
  return chars

applyRelation(rel, ref_char):
  ref_rank ← π⁻¹(ref_char)
  match rel:
    Same          → ref_rank
    Shift{δ}      → (ref_rank + δ) mod |Σ|
    Mirror        → (− ref_rank)  mod |Σ|
  return π( target_rank )

正準エンコーダの出力に対しては、 アンカーから両側に向かって 1 文字ずつ確定する 1 パスで復号が完了します(O(n))。 任意 DAG の場合でも、最悪 O(n²) で必ず収束します。

§06 · WORKED EXAMPLE

完全な例題

平文「あいしてる」(n = 5)を、アンカー位置 a = 2「し」で暗号化します。

−δ−δi=0i=1i=2 anchori=3i=4
平文 "あいしてる" のアンカー位置 i=2「し」を起点に、隣接位置に向かって 参照辺が伸びる。アンカーを起点に芋づる式に文字を復元できる。

簡単のため鍵 π を「default-v1 の標準順序」 (恒等置換) と仮定します。 すると五十音先頭付近のランクは次のようになります:

π⁻¹(あ) = 0   π⁻¹(い) = 1   π⁻¹(う) = 2
π⁻¹(え) = 3   π⁻¹(お) = 4   π⁻¹(か) = 5
…
π⁻¹(し) = 11  π⁻¹(す) = 12  π⁻¹(せ) = 13
…
π⁻¹(て) = 18  π⁻¹(と) = 19  π⁻¹(な) = 20
…
π⁻¹(る) = 38

正準エンコーダは次の参照を割り当てます:

位置 0 「あ」 → 参照 1 「い」
位置 1 「い」 → 参照 2 「し」(アンカー)
位置 2 「し」 → アンカー(None)
位置 3 「て」 → 参照 2 「し」(アンカー)
位置 4 「る」 → 参照 3 「て」

各関係の δ を計算します:

relations[0]: π⁻¹(あ)−π⁻¹(い) = 0−1 = −1 → Shift{ref=1, δ=−1}
relations[1]: π⁻¹(い)−π⁻¹(し) = 1−11 = −10 → Shift{ref=2, δ=−10}
relations[2]: None
relations[3]: π⁻¹(て)−π⁻¹(し) = 18−11 = 7 → Shift{ref=2, δ=7}
relations[4]: π⁻¹(る)−π⁻¹(て) = 38−18 = 20 → Shift{ref=3, δ=20}

暗号文 JSON は次のようになります:

{
  "version": 1,
  "alphabet": "default-v1",
  "length": 5,
  "anchor": { "position": 2, "character": "し" },
  "relations": [
    { "kind": "shift", "reference": 1, "delta": -1  },
    { "kind": "shift", "reference": 2, "delta": -10 },
    null,
    { "kind": "shift", "reference": 2, "delta":   7 },
    { "kind": "shift", "reference": 3, "delta":  20 }
  ]
}

受信者は π⁻¹(し) = 11 を起点に、 11+(−10)=1 から「い」、 11+7=18 から「て」、 1+(−1)=0 から「あ」、 18+20=38 から「る」を順に復元します。

§07 · LEAKAGE

情報漏れの分析

関係性暗号の出力からは、確かに以下が漏れています:

  • 平文長 n(v0.2 の noise injection で隠せる)。
  • アンカーの位置 a と文字 m_a。
  • 参照グラフの形(正準エンコーダなら隣接パス、 chain なら任意ランダム木)。
  • 各関係の kinddelta

逆に漏れていないのは:

  • 各非アンカー位置の文字そのもの
  • どの delta が「あ→い」を意味し、どれが「お→か」を意味するかの対応。 これは鍵 π のランク順に完全に依存します。

ただし──関係性暗号は単アルファベット換字と同等の頻度漏れを内在的に持ちます。 同じ文字対 (mᵢ, m_{rᵢ}) は同じ delta を生むため、 平文中に頻出する文字対は暗号文中の delta 分布のピークになります。 これが §09 で述べる脅威の主因です。

§08 · ORI v0.2

v0.2「織り」の拡張

多重結び (chain encoder)

正準エンコーダはアンカーを中心とした両側パスに参照を固定しますが、 chain エンコーダはアンカーを根とする一様ランダム木に拡張します。 アルゴリズムは Wilson のスパニング木生成の単純化版に相当します:

encrypt_chain(m, π, a, R):
  resolved ← {a};   pending ← shuffle(R, {0..n}\{a})
  for each i ∈ pending:
    rᵢ ← uniform_random(R, resolved)
    relations[i] ← chooseRelation(mᵢ, m_{rᵢ}, rᵢ)
    resolved ← resolved ∪ {i}
  ...emit ciphertext (version=1)

重要なのは、デコーダは無改修だということです。 v1 の復号アルゴリズム(トポロジカルソート)は アンカーを根とする任意の DAG を既に受理するため、 ciphertext の version1 のまま。 後方互換のオプトイン拡張です。

迷い糸 (noise injection)

平文にないダミー文字を暗号文に織り込み、平文長そのものを隠します。

encrypt_woven(m, π, a, k, R):
  N ← n + k
  σ : {0..n} → {0..N} を一様ランダムな単射で固定
  noise_slots ← {0..N} \ image(σ)
  各ノイズ位置 j に dⱼ ← uniform_random(R, Σ)
  full_chars[σ(i)] ← mᵢ;   full_chars[j] ← dⱼ
  cipher_anchor ← σ(a)
  全 N 位置を chain と同じ要領で結ぶ
  ext ← { plaintext_indices: [σ(0),σ(1),…,σ(n−1)] }
  return Ciphertext(length=N, anchor=cipher_anchor, …, ext)

ext.plaintext_indices は鍵を持つ受信者が、 復号後に「どのスロットが本物か」を識別するためだけのインデックス列です。 鍵を持たない第三者にとっては、N 個のノードはすべて同じように見えます。

§09 · THREAT MODEL

正直な脅威モデル

musubi の前提は 「ペンと紙を持った好奇心ある友人」 です。 国家規模の攻撃者ではありません。 古典暗号としての標準的な脆弱性をすべて持ちます。

  • 頻度分析: 同じ文字対は同じ delta を生むため、十分長い暗号文では delta のヒストグラムから 「e ↔ t」に相当する暗号文中のピークが浮き上がる。
  • 既知平文攻撃: 攻撃者が小さな平文片を知っていれば、 関係から鍵 π のいくつかの位置を直接決定できる。
  • 選択平文攻撃: 任意の平文を暗号化させられれば、π 全体を逆算できる。
  • アンカー暴露: アンカー文字は暗号文に常に 1 文字露出する。 これは仕様上の選択であって解析弱点ではないが、 「秘匿したい一文字を含めない」運用が前提。

v0.2 の noise injection は長さ秘匿構造ノイズを増やしますが、 上記の頻度・既知平文に対する根本耐性は変わりません。 musubi は本気の秘匿通信のためのソフトウェアではありません。 恋文、パズル、自作古典暗号の遊びのために使ってください。

§10 · COMPARISON

古典暗号との比較

暗号暗号文に書かれるもの
シーザー平文をシフト後の文字整数 1 つ
単アルファベット換字平文を別の文字に置換Σ 上の置換 π
ヴィジュネル鍵語と平文の差を文字化鍵語
転置平文を並べ替えただけ並べ替え順
関係性暗号 (musubi)アンカー 1 文字と、文字間の差 (関係)Σ 上の置換 π

関係性暗号は鍵としては単アルファベット換字と同じ「Σ 上の置換」を使いますが、 暗号文の表記レイヤが違います。 換字は文字を書く、関係性は差を書く。 表記レイヤを「文字」から「差」へ移したことで、 アンカー 1 文字を露出する代償と引き換えに、 各位置の文字そのものが暗号文から失われます。

§11 · OPEN PROBLEMS

未解決の問い

v0.3 以降に向けて検討中の方向性:

  • 五十音シフト (案 2): Shift δ を「鍵順での δ」だけでなく、 五十音表の(あ行・か行…)と(あ段・い段…)でも記述できるようにする。 響きや母音単位の関係を直接表現でき、文学的な暗号文が編める。 必要事項:Alphabet にグリッドメタデータを追加。
  • 双子星 / 複数アンカー (案 4): 長文を複数ブロックに分割し、 各ブロックに別個のアンカーを置く。 さらにアンカー間の関係を別鍵として渡せば「星座」 のような構造になる。 これは FORMAT_VERSION → 2 の破壊的変更を伴う v0.4 候補。
  • 清濁切替・同母音関係: 「が ↔ か」「し ↔ じ」のような濁音切替や、「あ ↔ か ↔ さ ↔ た …」 のような同段関係を新しい kind として追加。 五十音シフトの拡張上で自然に表現できる。
  • ロマン暗号としての出力フォーマット: JSON だけでなく、古文書風 / 俳句風 のテキスト出力。 「3番目は『し』。1番目はそこから 2 つ後…」のような自然言語化。

これらは リリースロードマップ および PROPOSAL-v0.2.md に詳細があります。

TRY IT

読むより、結んでみる。

ここで読んだアルゴリズムは、ブラウザの中で完全に走らせられます。 鍵もテキストも、サーバには送られません。