mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-07 21:07:59 +02:00
release: prepare v0.9.7
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""Phase 2 — per-alias HKDF-derived DM identity keys.
|
||||
|
||||
These tests pin the wire-level non-linkability invariant introduced in
|
||||
Phase 2: each alias gets its own Ed25519 public key derived deterministically
|
||||
from ``dm_identity.private_key`` via HKDF-SHA256. See
|
||||
``docs/mesh/wormhole-dm-root-operations-runbook.md`` §"Phase 2 — Per-Alias DM
|
||||
Identity Keys (HKDF-Derived)" for the design rationale.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def _fresh_persona_state(tmp_path, monkeypatch):
|
||||
from services.mesh import mesh_secure_storage, mesh_wormhole_persona
|
||||
|
||||
monkeypatch.setattr(mesh_secure_storage, "DATA_DIR", tmp_path)
|
||||
monkeypatch.setattr(
|
||||
mesh_secure_storage, "MASTER_KEY_FILE", tmp_path / "wormhole_secure_store.key"
|
||||
)
|
||||
monkeypatch.setattr(mesh_wormhole_persona, "DATA_DIR", tmp_path)
|
||||
monkeypatch.setattr(
|
||||
mesh_wormhole_persona, "PERSONA_FILE", tmp_path / "wormhole_persona.json"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
mesh_wormhole_persona,
|
||||
"LEGACY_DM_IDENTITY_FILE",
|
||||
tmp_path / "wormhole_identity.json",
|
||||
)
|
||||
return mesh_wormhole_persona
|
||||
|
||||
|
||||
def test_phase2_per_alias_keys_are_unlinkable(tmp_path, monkeypatch):
|
||||
"""Three aliases on the same node must yield three distinct public keys.
|
||||
|
||||
This is the wire-level non-linkability invariant: a passive observer who
|
||||
collects two signed alias bindings from the same node must not be able to
|
||||
correlate them via a shared dm_identity public key.
|
||||
"""
|
||||
|
||||
persona = _fresh_persona_state(tmp_path, monkeypatch)
|
||||
persona.bootstrap_wormhole_persona_state(force=True)
|
||||
|
||||
sig_a = persona.sign_dm_alias_blob("alias-aaaa", b"binding-payload-1")
|
||||
sig_b = persona.sign_dm_alias_blob("alias-bbbb", b"binding-payload-2")
|
||||
sig_c = persona.sign_dm_alias_blob("alias-cccc", b"binding-payload-3")
|
||||
|
||||
assert sig_a["ok"] is True
|
||||
assert sig_b["ok"] is True
|
||||
assert sig_c["ok"] is True
|
||||
|
||||
# Three distinct public keys — the linkability hole is closed.
|
||||
assert sig_a["public_key"] != sig_b["public_key"]
|
||||
assert sig_b["public_key"] != sig_c["public_key"]
|
||||
assert sig_a["public_key"] != sig_c["public_key"]
|
||||
|
||||
# Each per-alias key must also differ from the legacy singleton master key.
|
||||
state = persona.read_wormhole_persona_state()
|
||||
legacy_pub = str(state.get("dm_identity", {}).get("public_key", "") or "")
|
||||
assert legacy_pub
|
||||
assert sig_a["public_key"] != legacy_pub
|
||||
assert sig_b["public_key"] != legacy_pub
|
||||
assert sig_c["public_key"] != legacy_pub
|
||||
|
||||
# Cutover marker must be set after first per-alias derive.
|
||||
assert bool(state["dm_identity"].get("legacy_only")) is True
|
||||
|
||||
# The cache survived to disk.
|
||||
cached = state.get("dm_alias_keys") or {}
|
||||
assert set(cached.keys()) == {"alias-aaaa", "alias-bbbb", "alias-cccc"}
|
||||
assert cached["alias-aaaa"]["public_key"] == sig_a["public_key"]
|
||||
|
||||
|
||||
def test_phase2_per_alias_key_is_deterministic(tmp_path, monkeypatch):
|
||||
"""Re-signing the same alias must produce the same public key.
|
||||
|
||||
Determinism is what lets historical alias bindings remain verifiable
|
||||
across restarts without persisting per-alias private keys.
|
||||
"""
|
||||
|
||||
persona = _fresh_persona_state(tmp_path, monkeypatch)
|
||||
persona.bootstrap_wormhole_persona_state(force=True)
|
||||
|
||||
first = persona.sign_dm_alias_blob("alice", b"payload-1")
|
||||
second = persona.sign_dm_alias_blob("alice", b"payload-2")
|
||||
|
||||
assert first["ok"] is True
|
||||
assert second["ok"] is True
|
||||
assert first["public_key"] == second["public_key"]
|
||||
# Different payloads → different signatures (sanity).
|
||||
assert first["signature"] != second["signature"]
|
||||
|
||||
# Both signatures verify under the per-alias path.
|
||||
ok1, _ = persona.verify_dm_alias_blob("alice", b"payload-1", first["signature"])
|
||||
ok2, _ = persona.verify_dm_alias_blob("alice", b"payload-2", second["signature"])
|
||||
assert ok1 is True
|
||||
assert ok2 is True
|
||||
|
||||
# And cross-payload signatures must NOT verify (binding integrity).
|
||||
bad, _ = persona.verify_dm_alias_blob("alice", b"payload-1", second["signature"])
|
||||
assert bad is False
|
||||
|
||||
|
||||
def test_phase2_legacy_signature_still_verifies(tmp_path, monkeypatch):
|
||||
"""A signature produced by the pre-Phase-2 singleton path must still
|
||||
verify via :func:`verify_dm_alias_blob`'s legacy fallback branch when
|
||||
``dm_identity.legacy_only`` is true.
|
||||
|
||||
This is the historical-verifiability invariant: alias bindings already
|
||||
published with the singleton key remain verifiable forever.
|
||||
"""
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||
|
||||
persona = _fresh_persona_state(tmp_path, monkeypatch)
|
||||
persona.bootstrap_wormhole_persona_state(force=True)
|
||||
|
||||
# Manually produce a "pre-Phase-2" signature using the legacy singleton
|
||||
# private key (simulating a historical record on disk).
|
||||
state = persona.read_wormhole_persona_state()
|
||||
identity = state["dm_identity"]
|
||||
legacy_priv_b64 = str(identity.get("private_key", "") or "")
|
||||
assert legacy_priv_b64
|
||||
|
||||
legacy_priv = ed25519.Ed25519PrivateKey.from_private_bytes(
|
||||
persona._unb64(legacy_priv_b64)
|
||||
)
|
||||
bound = persona._bound_dm_alias_blob("legacy-alias", b"historical-payload", legacy=True)
|
||||
legacy_signature = legacy_priv.sign(bound).hex()
|
||||
|
||||
# Trigger the cutover marker by signing once via the new path with
|
||||
# *some* alias — this is what flips dm_identity.legacy_only=True.
|
||||
persona.sign_dm_alias_blob("phase2-trigger", b"trigger-payload")
|
||||
|
||||
# The legacy signature must verify via the fallback branch.
|
||||
ok, reason = persona.verify_dm_alias_blob(
|
||||
"legacy-alias", b"historical-payload", legacy_signature
|
||||
)
|
||||
assert ok is True, f"legacy fallback failed: {reason}"
|
||||
|
||||
# Tampered legacy signature must NOT verify.
|
||||
tampered = legacy_signature[:-2] + ("00" if legacy_signature[-2:] != "00" else "01")
|
||||
bad, _ = persona.verify_dm_alias_blob(
|
||||
"legacy-alias", b"historical-payload", tampered
|
||||
)
|
||||
assert bad is False
|
||||
Reference in New Issue
Block a user