Part of #33. Built on top of #TBD (PQ-2).
Deliverables
New module switchboard/pq_keys.py:
@dataclass
class PQKeyPair:
alg: str
sk: bytes
pk: bytes
key_id: str # sha256(pk)[:16] hex
@classmethod
def generate(cls, alg: str = "ml-dsa-65") -> "PQKeyPair": ...
@classmethod
def load(cls, path: str, passphrase: bytes | None = None) -> "PQKeyPair": ...
def save(self, path: str, passphrase: bytes | None = None) -> None: ...
def sign(self, transcript: bytes) -> bytes: ...
def verify(alg: str, pk: bytes, transcript: bytes, sig: bytes) -> bool: ...
Storage format on disk: PEM-like envelope:
-----BEGIN SWITCHBOARD PQ KEY-----
alg: ml-dsa-65
kdf: scrypt
kdf-salt: <hex>
kdf-n: 32768
kdf-r: 8
kdf-p: 1
cipher: chacha20-poly1305
cipher-nonce: <hex>
cipher-tag: <hex>
<base64 ciphertext of sk>
-----END SWITCHBOARD PQ KEY-----
Public-key file is a separate .pub with the raw pk + alg header.
Tests
- Generate, save, load round-trip.
- Wrong passphrase → loud failure, not silent corruption.
key_id deterministic and stable across runs.
Done when
tests/test_pq_keys.py green.
pq_keys.py is import-safe without liboqs (raises only at .generate()/.sign() time).
Depends on: #TBD (PQ-2).
🤖 Generated with Claude Code
Part of #33. Built on top of #TBD (PQ-2).
Deliverables
New module
switchboard/pq_keys.py:Storage format on disk: PEM-like envelope:
Public-key file is a separate
.pubwith the rawpk+algheader.Tests
key_iddeterministic and stable across runs.Done when
tests/test_pq_keys.pygreen.pq_keys.pyis import-safe withoutliboqs(raises only at.generate()/.sign()time).Depends on: #TBD (PQ-2).
🤖 Generated with Claude Code