You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document analyzes two TODO/FIXME issues in the noise security module:
Message prefix behavior in Go - libp2p/security/noise/io.py:44
Static key storage for IK pattern - libp2p/security/noise/transport.py:35
Issue 1: Message Prefix Behavior in Go
Issue Location
File:libp2p/security/noise/io.py:44 Class:BaseNoiseMsgReadWriter FIXME Comment:# FIXME: This prefix is added in msg#3 in Go. Check whether it's a desired behavior.
Current Status Analysis
Current Implementation
The BaseNoiseMsgReadWriter class includes a hardcoded prefix for message handling:
classBaseNoiseMsgReadWriter(EncryptedMsgReadWriter):
""" The base implementation of noise message reader/writer. """read_writer: NoisePacketReadWriternoise_state: NoiseState# FIXME: This prefix is added in msg#3 in Go. Check whether it's a desired behavior.prefix: bytes=b"\x00"*32def__init__(self, conn: IRawConnection, noise_state: NoiseState) ->None:
self.read_writer=NoisePacketReadWriter(cast(ReadWriteCloser, conn))
self.noise_state=noise_stateasyncdefwrite_msg(self, msg: bytes, prefix_encoded: bool=False) ->None:
data_encrypted=self.encrypt(msg)
ifprefix_encoded:
# Manually add the prefix if neededdata_encrypted=self.prefix+data_encryptedawaitself.read_writer.write_msg(data_encrypted)
asyncdefread_msg(self, prefix_encoded: bool=False) ->bytes:
noise_msg_encrypted=awaitself.read_writer.read_msg()
ifprefix_encoded:
returnself.decrypt(noise_msg_encrypted[len(self.prefix) :])
else:
returnself.decrypt(noise_msg_encrypted)
The Problem
Inconsistency: The prefix behavior differs from Go implementation
Unclear Purpose: The 32-byte zero prefix's purpose is not documented
Conditional Usage: The prefix is only used when prefix_encoded=True
Interoperability Risk: May cause issues with Go libp2p nodes
What Must Be Done
Required Investigation
Go Implementation Analysis: Understand how Go handles message prefixes
Protocol Specification: Check if this is part of the Noise protocol spec
Interoperability Testing: Test with Go libp2p nodes
Documentation: Clarify the purpose and usage of the prefix
Implementation Options
Option A: Remove Prefix (If Not Needed)
classBaseNoiseMsgReadWriter(EncryptedMsgReadWriter):
""" The base implementation of noise message reader/writer. """read_writer: NoisePacketReadWriternoise_state: NoiseStatedef__init__(self, conn: IRawConnection, noise_state: NoiseState) ->None:
self.read_writer=NoisePacketReadWriter(cast(ReadWriteCloser, conn))
self.noise_state=noise_stateasyncdefwrite_msg(self, msg: bytes) ->None:
data_encrypted=self.encrypt(msg)
awaitself.read_writer.write_msg(data_encrypted)
asyncdefread_msg(self) ->bytes:
noise_msg_encrypted=awaitself.read_writer.read_msg()
returnself.decrypt(noise_msg_encrypted)
Option B: Make Prefix Configurable
classBaseNoiseMsgReadWriter(EncryptedMsgReadWriter):
""" The base implementation of noise message reader/writer. """read_writer: NoisePacketReadWriternoise_state: NoiseStatedef__init__(
self,
conn: IRawConnection,
noise_state: NoiseState,
message_prefix: bytes|None=None
) ->None:
self.read_writer=NoisePacketReadWriter(cast(ReadWriteCloser, conn))
self.noise_state=noise_stateself.message_prefix=message_prefixorb"\x00"*32asyncdefwrite_msg(self, msg: bytes, use_prefix: bool=False) ->None:
data_encrypted=self.encrypt(msg)
ifuse_prefix:
data_encrypted=self.message_prefix+data_encryptedawaitself.read_writer.write_msg(data_encrypted)
asyncdefread_msg(self, expect_prefix: bool=False) ->bytes:
noise_msg_encrypted=awaitself.read_writer.read_msg()
ifexpect_prefix:
iflen(noise_msg_encrypted) <len(self.message_prefix):
raiseValueError("Message too short for expected prefix")
returnself.decrypt(noise_msg_encrypted[len(self.message_prefix):])
else:
returnself.decrypt(noise_msg_encrypted)
Option C: Protocol-Specific Prefix Handling
classBaseNoiseMsgReadWriter(EncryptedMsgReadWriter):
""" The base implementation of noise message reader/writer. """read_writer: NoisePacketReadWriternoise_state: NoiseStatedef__init__(
self,
conn: IRawConnection,
noise_state: NoiseState,
protocol_name: str="Noise_XX_25519_ChaChaPoly_SHA256"
) ->None:
self.read_writer=NoisePacketReadWriter(cast(ReadWriteCloser, conn))
self.noise_state=noise_stateself.protocol_name=protocol_name# Determine prefix based on protocolself.message_prefix=self._get_protocol_prefix()
def_get_protocol_prefix(self) ->bytes:
"""Get the appropriate prefix for the protocol."""# This would be determined by protocol analysisif"XX"inself.protocol_name:
returnb"\x00"*32# XX pattern prefixelif"IK"inself.protocol_name:
returnb""# IK pattern no prefixelse:
returnb""# Default no prefixasyncdefwrite_msg(self, msg: bytes, message_number: int|None=None) ->None:
data_encrypted=self.encrypt(msg)
# Add prefix based on message number and protocolifmessage_number==3andself.message_prefix:
data_encrypted=self.message_prefix+data_encryptedawaitself.read_writer.write_msg(data_encrypted)
asyncdefread_msg(self, message_number: int|None=None) ->bytes:
noise_msg_encrypted=awaitself.read_writer.read_msg()
# Remove prefix based on message number and protocolifmessage_number==3andself.message_prefix:
iflen(noise_msg_encrypted) <len(self.message_prefix):
raiseValueError("Message too short for expected prefix")
returnself.decrypt(noise_msg_encrypted[len(self.message_prefix):])
else:
returnself.decrypt(noise_msg_encrypted)
Impact Analysis
Interoperability: High - Affects compatibility with other implementations
Protocol Compliance: High - Must match Noise protocol specification
Complexity: Low - Simple prefix handling
Testing: Medium - Requires cross-implementation testing
Issue 2: Static Key Storage for IK Pattern
Issue Location
File:libp2p/security/noise/transport.py:35 Class:Transport TODO Comment:# TODO: A storage of seen noise static keys for pattern IK?
Current Status Analysis
Current Implementation
The Transport class currently doesn't implement noise pipes or IK pattern support:
classTransport(ISecureTransport):
libp2p_privkey: PrivateKeynoise_privkey: PrivateKeylocal_peer: IDearly_data: bytes|Nonewith_noise_pipes: bool# NOTE: Implementations that support Noise Pipes must decide whether to use# an XX or IK handshake based on whether they possess a cached static# Noise key for the remote peer.# TODO: A storage of seen noise static keys for pattern IK?def__init__(
self,
libp2p_keypair: KeyPair,
noise_privkey: PrivateKey,
early_data: bytes|None=None,
with_noise_pipes: bool=False,
) ->None:
self.libp2p_privkey=libp2p_keypair.private_keyself.noise_privkey=noise_privkeyself.local_peer=ID.from_pubkey(libp2p_keypair.public_key)
self.early_data=early_dataself.with_noise_pipes=with_noise_pipesifself.with_noise_pipes:
raiseNotImplementedErrordefget_pattern(self) ->IPattern:
ifself.with_noise_pipes:
raiseNotImplementedErrorelse:
returnPatternXX(
self.local_peer,
self.libp2p_privkey,
self.noise_privkey,
self.early_data,
)
The Problem
Noise Pipes Not Implemented: The feature is disabled with NotImplementedError
Missing IK Pattern: No implementation of the IK handshake pattern
No Key Caching: No storage mechanism for static keys
Performance Impact: Can't optimize handshakes with known peers
What Must Be Done
Required Changes
Static Key Storage: Implement a cache for remote peer static keys
IK Pattern Implementation: Create PatternIK class
Pattern Selection Logic: Choose between XX and IK based on cached keys
Key Management: Handle key storage, retrieval, and expiration
Implementation Options
Option A: Simple In-Memory Key Cache
importtimefromtypingimportDict, OptionalclassTransport(ISecureTransport):
libp2p_privkey: PrivateKeynoise_privkey: PrivateKeylocal_peer: IDearly_data: bytes|Nonewith_noise_pipes: bool_static_key_cache: Dict[ID, tuple[PublicKey, float]] # peer_id -> (key, timestamp)def__init__(
self,
libp2p_keypair: KeyPair,
noise_privkey: PrivateKey,
early_data: bytes|None=None,
with_noise_pipes: bool=False,
cache_ttl: int=3600, # 1 hour default
) ->None:
self.libp2p_privkey=libp2p_keypair.private_keyself.noise_privkey=noise_privkeyself.local_peer=ID.from_pubkey(libp2p_keypair.public_key)
self.early_data=early_dataself.with_noise_pipes=with_noise_pipesself.cache_ttl=cache_ttlself._static_key_cache= {}
def_get_cached_static_key(self, peer_id: ID) ->Optional[PublicKey]:
"""Get cached static key for a peer if available and not expired."""ifpeer_idnotinself._static_key_cache:
returnNonekey, timestamp=self._static_key_cache[peer_id]
iftime.time() -timestamp>self.cache_ttl:
delself._static_key_cache[peer_id]
returnNonereturnkeydef_cache_static_key(self, peer_id: ID, static_key: PublicKey) ->None:
"""Cache a static key for a peer."""self._static_key_cache[peer_id] = (static_key, time.time())
defget_pattern(self, remote_peer: ID|None=None) ->IPattern:
ifnotself.with_noise_pipes:
returnPatternXX(
self.local_peer,
self.libp2p_privkey,
self.noise_privkey,
self.early_data,
)
# Check if we have a cached static key for IK patternifremote_peerandself._get_cached_static_key(remote_peer):
returnPatternIK(
self.local_peer,
self.libp2p_privkey,
self.noise_privkey,
self.early_data,
remote_peer,
self._get_cached_static_key(remote_peer),
)
else:
returnPatternXX(
self.local_peer,
self.libp2p_privkey,
self.noise_privkey,
self.early_data,
)
asyncdefsecure_outbound(self, conn: IRawConnection, peer_id: ID) ->ISecureConn:
pattern=self.get_pattern(peer_id)
secure_conn=awaitpattern.handshake_outbound(conn, peer_id)
# Cache the static key if using XX pattern (we learned it)ifisinstance(pattern, PatternXX):
# Extract static key from the handshakestatic_key=self._extract_static_key_from_handshake(pattern)
ifstatic_key:
self._cache_static_key(peer_id, static_key)
returnsecure_conn
Option B: Persistent Key Storage
importsqlite3importjsonfromtypingimportOptionalclassStaticKeyStorage:
def__init__(self, db_path: str="noise_static_keys.db"):
self.db_path=db_pathself._init_db()
def_init_db(self) ->None:
"""Initialize the database schema."""withsqlite3.connect(self.db_path) asconn:
conn.execute(""" CREATE TABLE IF NOT EXISTS static_keys ( peer_id TEXT PRIMARY KEY, static_key BLOB, created_at INTEGER, last_used INTEGER, use_count INTEGER DEFAULT 0 ) """)
defget_static_key(self, peer_id: ID) ->Optional[PublicKey]:
"""Get cached static key for a peer."""withsqlite3.connect(self.db_path) asconn:
cursor=conn.execute(""" SELECT static_key, created_at FROM static_keys WHERE peer_id = ? """, (str(peer_id),))
row=cursor.fetchone()
ifrow:
static_key_bytes, created_at=row# Check if key is not too old (e.g., 24 hours)iftime.time() -created_at<86400:
# Update usage statisticsconn.execute(""" UPDATE static_keys SET last_used = ?, use_count = use_count + 1 WHERE peer_id = ? """, (int(time.time()), str(peer_id)))
returnself._deserialize_public_key(static_key_bytes)
returnNonedefstore_static_key(self, peer_id: ID, static_key: PublicKey) ->None:
"""Store a static key for a peer."""withsqlite3.connect(self.db_path) asconn:
conn.execute(""" INSERT OR REPLACE INTO static_keys (peer_id, static_key, created_at, last_used) VALUES (?, ?, ?, ?) """, (
str(peer_id),
static_key.to_bytes(),
int(time.time()),
int(time.time())
))
defcleanup_expired_keys(self, max_age: int=86400) ->None:
"""Remove expired keys from storage."""withsqlite3.connect(self.db_path) asconn:
conn.execute(""" DELETE FROM static_keys WHERE ? - created_at > ? """, (int(time.time()), max_age))
classTransport(ISecureTransport):
def__init__(
self,
libp2p_keypair: KeyPair,
noise_privkey: PrivateKey,
early_data: bytes|None=None,
with_noise_pipes: bool=False,
key_storage: StaticKeyStorage|None=None,
) ->None:
# ... existing initialization ...self.key_storage=key_storageorStaticKeyStorage()
self.with_noise_pipes=with_noise_pipesdefget_pattern(self, remote_peer: ID|None=None) ->IPattern:
ifnotself.with_noise_pipes:
returnPatternXX(...)
# Check persistent storage for static keyifremote_peer:
cached_key=self.key_storage.get_static_key(remote_peer)
ifcached_key:
returnPatternIK(
self.local_peer,
self.libp2p_privkey,
self.noise_privkey,
self.early_data,
remote_peer,
cached_key,
)
returnPatternXX(...)
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
TODO Analysis: Noise Security Issues
Overview
This document analyzes two TODO/FIXME issues in the noise security module:
libp2p/security/noise/io.py:44
libp2p/security/noise/transport.py:35
Issue 1: Message Prefix Behavior in Go
Issue Location
File:
libp2p/security/noise/io.py:44
Class:
BaseNoiseMsgReadWriter
FIXME Comment:
# FIXME: This prefix is added in msg#3 in Go. Check whether it's a desired behavior.
Current Status Analysis
Current Implementation
The
BaseNoiseMsgReadWriter
class includes a hardcoded prefix for message handling:The Problem
prefix_encoded=True
What Must Be Done
Required Investigation
Implementation Options
Option A: Remove Prefix (If Not Needed)
Option B: Make Prefix Configurable
Option C: Protocol-Specific Prefix Handling
Impact Analysis
Issue 2: Static Key Storage for IK Pattern
Issue Location
File:
libp2p/security/noise/transport.py:35
Class:
Transport
TODO Comment:
# TODO: A storage of seen noise static keys for pattern IK?
Current Status Analysis
Current Implementation
The
Transport
class currently doesn't implement noise pipes or IK pattern support:The Problem
NotImplementedError
What Must Be Done
Required Changes
PatternIK
classImplementation Options
Option A: Simple In-Memory Key Cache
Option B: Persistent Key Storage
Option C: Advanced PatternIK Implementation
Impact Analysis
Summary and Recommendations
Priority Order
High Priority: Message prefix behavior investigation (Issue 1)
Medium Priority: Static key storage for IK pattern (Issue 2)
Implementation Strategy
Testing Requirements
Documentation Needs
Beta Was this translation helpful? Give feedback.
All reactions