A Python port of the Invisible Internet Project (I2P)
at main 81 lines 2.8 kB view raw
1"""NTCP2 wire-format frame encryption. 2 3Implements the NTCP2 wire format where each frame uses TWO AEAD 4encrypt operations with the CipherState: 5 61. The frame length (2 bytes, big-endian) is encrypted first, 7 producing 18 bytes (2 plaintext + 16 AEAD tag). 82. The frame payload (type + length + data) is encrypted next, 9 producing N + 16 bytes (N plaintext + 16 AEAD tag). 10 11Each operation consumes one nonce from the CipherState, so each 12frame advances the nonce counter by 2. 13""" 14 15import struct 16 17from i2p_crypto.noise import CipherState 18from i2p_transport.ntcp2 import NTCP2Frame 19 20 21class NTCP2WireCodec: 22 """Encrypts and decrypts NTCP2 frames using the two-operation wire format.""" 23 24 def encrypt_frame(self, cipher: CipherState, frame: NTCP2Frame) -> bytes: 25 """Encrypt an NTCP2Frame into wire-format bytes. 26 27 Args: 28 cipher: CipherState with key set (post-handshake). 29 frame: The frame to encrypt. 30 31 Returns: 32 Wire bytes: encrypted_length (18) + encrypted_payload (N + 16). 33 """ 34 frame_bytes = frame.to_bytes() 35 36 # Operation 1: encrypt the 2-byte frame length 37 length_plaintext = struct.pack("!H", len(frame_bytes)) 38 encrypted_length = cipher.encrypt_with_ad(b"", length_plaintext) 39 40 # Operation 2: encrypt the frame bytes 41 encrypted_payload = cipher.encrypt_with_ad(b"", frame_bytes) 42 43 return encrypted_length + encrypted_payload 44 45 def decrypt_frame_length(self, cipher: CipherState, encrypted_length: bytes) -> int: 46 """Decrypt the 18-byte encrypted length field. 47 48 Args: 49 cipher: CipherState with matching key/nonce. 50 encrypted_length: 18 bytes (2 plaintext + 16 AEAD tag). 51 52 Returns: 53 The frame length as an integer. 54 """ 55 length_bytes = cipher.decrypt_with_ad(b"", encrypted_length) 56 return struct.unpack("!H", length_bytes)[0] 57 58 def decrypt_frame_payload(self, cipher: CipherState, encrypted_payload: bytes) -> NTCP2Frame: 59 """Decrypt the encrypted payload and parse it as an NTCP2Frame. 60 61 Args: 62 cipher: CipherState with matching key/nonce (after length decrypt). 63 encrypted_payload: The encrypted frame bytes (N + 16 bytes). 64 65 Returns: 66 The decrypted NTCP2Frame. 67 """ 68 frame_bytes = cipher.decrypt_with_ad(b"", encrypted_payload) 69 return NTCP2Frame.from_bytes(frame_bytes) 70 71 def encrypt_and_get_wire_bytes(self, cipher: CipherState, frame: NTCP2Frame) -> bytes: 72 """Convenience method: same as encrypt_frame. 73 74 Args: 75 cipher: CipherState with key set. 76 frame: The frame to encrypt. 77 78 Returns: 79 Full wire bytes for transmission. 80 """ 81 return self.encrypt_frame(cipher, frame)