diff --git a/quantumpipe/handshake.py b/quantumpipe/handshake.py index 766c427..a90fc03 100644 --- a/quantumpipe/handshake.py +++ b/quantumpipe/handshake.py @@ -23,11 +23,11 @@ from .chab3 import decrypt, encrypt, random from .exceptions import _Burn from .kdf import kdf -# -- fixed-size record layouts (struct handles packing + length checks) ----- -# hybrid scheme = ML-KEM-768 (pk 1184 / sk-seed 64 / ct 1088) || X25519 (32) -_TAG = struct.Struct("32s") # bare kdf tag +# -- Fixed-size record layouts (struct handles packing + length checks) ----- +# Hybrid scheme = ML-KEM-768 (pk 1184 / sk-seed 64 / ct 1088) || X25519 (32) +_TAG = struct.Struct("32s") # Bare kdf tag -# handshake message bodies; box(N) is a chab3 box of N plaintext bytes (N+28) +# Handshake message bodies; box(N) is a chab3 box of N plaintext bytes (N+28) _MSG1_REGISTER = struct.Struct("1184s32s32s") # e_alice_m | e_alice_x | commitment _MSG2_REGISTER = struct.Struct( "32s1088s1244s" @@ -47,7 +47,7 @@ _MSG2_RESUME = struct.Struct("32s1088s1088s32s") # e_bob | sa_ct | ee_ct | tb # -------------------------------------------------------------------------- -# hybrid KEM: ML-KEM-768 || X25519 +# Hybrid KEM: ML-KEM-768 || X25519 # # A static private key is a single 32-byte idroot secret: each scheme's # key is derived from it with one domain-separated kdf call, so the two @@ -55,8 +55,8 @@ _MSG2_RESUME = struct.Struct("32s1088s1088s32s") # e_bob | sa_ct | ee_ct | tb # held (and the only thing the credential stores). Ephemeral keys are # generated directly -- nothing of them persists. # -# Each party ships one X25519 ephemeral public per connection (alice's -# inside e_alice in msg1, bob's as e_bob in msg2); every X25519 leg is a +# Each party ships one X25519 ephemeral public per connection (Alice's +# inside e_alice in msg1, Bob's as e_bob in msg2); every X25519 leg is a # plain DH against those shipped keys. ML-KEM legs ship as bare ML-KEM # ciphertexts. @@ -102,13 +102,13 @@ def _check(ok: bool, why: str) -> None: def open_hello(msg1: bytes, *, shared: bytes, ad: bytes, flow: str) -> bytes: - # open a msg1 box under a candidate credential's shared key (the + # Open a msg1 box under a candidate credential's shared key (the # responder's caller scan); ValueError = not this credential return decrypt(msg1, key=kdf(flow + "/msg1", shared), ad=ad) # -------------------------------------------------------------------------- -# ad-chained boxes: each box's associated data is the previous box's tag +# AD-chained boxes: each box's associated data is the previous box's tag class _Chain: @@ -126,14 +126,14 @@ class _Chain: return plain def advance(self, msg: bytes) -> None: - # a box decrypted outside the chain (the responder's caller scan) + # A box decrypted outside the chain (the responder's caller scan) self._ad = msg[-16:] # -------------------------------------------------------------------------- -# the two exchanges. Stage names carry the acting party: -# alice drives with startA/respondA (+ confirmA when registering), -# bob with respondB (+ confirmB/finishB when registering, completeB +# The two exchanges. Stage names carry the acting party: +# Alice drives with startA/respondA (+ confirmA when registering), +# Bob with respondB (+ confirmB/finishB when registering, completeB # when resuming) @@ -147,7 +147,7 @@ class Registration: def __init__(self, shared: bytes, idroot: bytes, ad: bytes): self._shared = shared self._idroot = idroot - # static key objects, derived once from the persisted idroot + # Static key objects, derived once from the persisted idroot self._mlkem = _mlkem_private_key(idroot) self._x25519 = _x25519_private_key(idroot) self._static_m, self._static_x = _public_from_idroot(idroot) @@ -168,7 +168,7 @@ class Registration: return self._chain.encrypt(body, key=kdf("reg/msg1", self._shared)) def respondA(self, msg2: bytes) -> bytes: - # open bob's reply -- an outer AEAD failure burns nothing, it + # Open Bob's reply -- an outer AEAD failure burns nothing, it # proves no key knowledge -- then challenge his static plain = self._chain.decrypt(msg2, key=kdf("reg/msg2", self._shared)) try: @@ -176,11 +176,11 @@ class Registration: # ee is forward secrecy, made to our connection key only ee = self._eph_m.decapsulate(ee_ct) ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_bob)) - # solving ee lets us read bob's static from the inner box + # Solving ee lets us read Bob's static from the inner box s_bob_m, s_bob_x = _MSG2_REGISTER_INNER.unpack( decrypt(inner, key=kdf("reg/msg2/inner", ee)) ) - # we challenge his static: only the real bob can solve this + # We challenge his static: only the real Bob can solve this es, es_ct = MLKEM768PublicKey.from_public_bytes(s_bob_m).encapsulate() es += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(s_bob_x)) except (struct.error, ValueError) as e: @@ -199,12 +199,12 @@ class Registration: ) def confirmA(self, msg4: bytes) -> tuple[bytes, bytes]: - # bob's challenge to our static plus his root confirmation. The + # Bob's challenge to our static plus his root confirmation. The # caller gates the answer (msg5) on the out-of-band comparison. plain = self._chain.decrypt(msg4, key=kdf("reg/msg4", self._shared, self._ee)) try: se_ct, tb = _MSG4_REGISTER.unpack(plain) - # solving se proves to bob that we hold alice's static + # Solving se proves to Bob that we hold Alice's static se = self._mlkem.decapsulate(se_ct) se += self._x25519.exchange(X25519PublicKey.from_public_bytes(self._e_bob)) except (struct.error, ValueError) as e: @@ -225,13 +225,13 @@ class Registration: def respondB(self, msg1: bytes, opened: bytes) -> bytes: # msg2 from an opened msg1 (open_hello did the caller scan); our - # connection key ships here once, alice answers every leg with it + # connection key ships here once, Alice answers every leg with it self._chain.advance(msg1) try: e_alice_m, e_alice_x, commitment = _MSG1_REGISTER.unpack(opened) self._ephemeral = X25519PrivateKey.generate() e_bob = self._ephemeral.public_key().public_bytes_raw() - # forward secrecy, made to alice's connection key only + # Forward secrecy, made to Alice's connection key only ee, ee_ct = MLKEM768PublicKey.from_public_bytes(e_alice_m).encapsulate() ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_alice_x)) except (struct.error, ValueError) as e: @@ -249,7 +249,7 @@ class Registration: return self._chain.encrypt(body, key=kdf("reg/msg2", self._shared)) def confirmB(self, msg3: bytes) -> tuple[bytes, bytes]: - # alice's challenge to our static, her static and her reveal. The + # Alice's challenge to our static, her static and her reveal. The # caller gates the answer (msg4) on the out-of-band comparison. plain = self._chain.decrypt(msg3, key=kdf("reg/msg3", self._shared, self._ee)) try: @@ -285,7 +285,7 @@ class Registration: return msg4, sas def finishB(self, msg5: bytes) -> None: - # alice's root confirmation: proves she solved our se challenge + # Alice's root confirmation: proves she solved our se challenge plain = self._chain.decrypt( msg5, key=kdf("reg/msg5", self._shared, self._ee, self._es) ) @@ -311,7 +311,7 @@ class Registration: class Resumption: """Resumption exchange: statics proven by decapsulation, never on - the wire; all legs hybrid. Alice: startA/respondA; bob: + the wire; all legs hybrid. Alice: startA/respondA; Bob: respondB/completeB; finish() -> (send, recv).""" def __init__(self, shared: bytes, idroot: bytes, peer_pub: tuple, ad: bytes): @@ -325,7 +325,7 @@ class Resumption: # -- alice ---------------------------------------------------------------- def startA(self) -> bytes: - # msg1: our connection keys plus a challenge to bob's static + # msg1: our connection keys plus a challenge to Bob's static self._eph_m, self._ephemeral, e_alice_m, e_alice_x = _fresh_ephemeral() peer_m, peer_x = self._peer_pub sb, sb_ct = MLKEM768PublicKey.from_public_bytes(peer_m).encapsulate() @@ -335,11 +335,11 @@ class Resumption: return self._chain.encrypt(body, key=kdf("res/msg1", self._shared)) def respondA(self, msg2: bytes) -> bytes: - # open bob's reply and solve his three challenges + # Open Bob's reply and solve his three challenges plain = self._chain.decrypt(msg2, key=kdf("res/msg2", self._shared, self._sb)) try: e_bob, sa_ct, ee_ct, tb = _MSG2_RESUME.unpack(plain) - # solving sa proves to bob that we hold alice's static + # Solving sa proves to Bob that we hold Alice's static sa = self._mlkem.decapsulate(sa_ct) sa += self._x25519.exchange(X25519PublicKey.from_public_bytes(e_bob)) # ee is forward secrecy, made to our connection key @@ -361,20 +361,20 @@ class Resumption: # -- bob ------------------------------------------------------------------ def respondB(self, msg1: bytes, opened: bytes) -> bytes: - # prove our static, challenge alice's, forward secrecy + # Prove our static, challenge Alice's, forward secrecy self._chain.advance(msg1) try: e_alice_m, e_alice_x, sb_ct = _MSG1_RESUME.unpack(opened) - # solving sb proves to alice that we hold bob's static + # Solving sb proves to Alice that we hold Bob's static sb = self._mlkem.decapsulate(sb_ct) sb += self._x25519.exchange(X25519PublicKey.from_public_bytes(e_alice_x)) self._ephemeral = X25519PrivateKey.generate() e_bob = self._ephemeral.public_key().public_bytes_raw() peer_m, peer_x = self._peer_pub - # we challenge alice's static the same way + # We challenge Alice's static the same way sa, sa_ct = MLKEM768PublicKey.from_public_bytes(peer_m).encapsulate() sa += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(peer_x)) - # and make forward secrecy to her connection key + # And make forward secrecy to her connection key ee, ee_ct = MLKEM768PublicKey.from_public_bytes(e_alice_m).encapsulate() ee += self._ephemeral.exchange(X25519PublicKey.from_public_bytes(e_alice_x)) except (struct.error, ValueError) as e: @@ -386,7 +386,7 @@ class Resumption: return self._chain.encrypt(body, key=kdf("res/msg2", self._shared, sb)) def completeB(self, msg3: bytes) -> None: - # alice answers with just her tag + # Alice answers with just her tag plain = self._chain.decrypt( msg3, key=kdf("res/msg3", self._shared, self._sb, self._sa, self._ee) )