From e4ab3cb2c5ac1ebaf07138c215daeb5755b999e7 Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Tue, 19 Aug 2025 04:41:14 +0530 Subject: [PATCH 1/2] Add early data support to Noise protocol Signed-off-by: varun-r-mallya --- .../doc-examples/example_encryption_noise.py | 4 +- examples/doc-examples/example_multiplexer.py | 4 +- .../doc-examples/example_peer_discovery.py | 4 +- examples/doc-examples/example_running.py | 4 +- libp2p/security/noise/early_data.py | 68 ++++++++++ libp2p/security/noise/patterns.py | 120 ++++++++++++++++-- libp2p/security/noise/pb/noise.proto | 12 +- libp2p/security/noise/pb/noise_pb2.py | 8 +- libp2p/security/noise/pb/noise_pb2.pyi | 25 +++- libp2p/security/noise/transport.py | 40 +++--- tests/utils/factories.py | 3 +- 11 files changed, 237 insertions(+), 55 deletions(-) create mode 100644 libp2p/security/noise/early_data.py diff --git a/examples/doc-examples/example_encryption_noise.py b/examples/doc-examples/example_encryption_noise.py index a2a4318c5..31d80580f 100644 --- a/examples/doc-examples/example_encryption_noise.py +++ b/examples/doc-examples/example_encryption_noise.py @@ -28,9 +28,7 @@ async def main(): noise_privkey=key_pair.private_key, # early_data: Optional data to send during the handshake # (None means no early data) - early_data=None, - # with_noise_pipes: Whether to use Noise pipes for additional security features - with_noise_pipes=False, + # TODO: add early data ) # Create a security options dictionary mapping protocol ID to transport diff --git a/examples/doc-examples/example_multiplexer.py b/examples/doc-examples/example_multiplexer.py index 0d6f26622..085244d29 100644 --- a/examples/doc-examples/example_multiplexer.py +++ b/examples/doc-examples/example_multiplexer.py @@ -28,9 +28,7 @@ async def main(): noise_privkey=key_pair.private_key, # early_data: Optional data to send during the handshake # (None means no early data) - early_data=None, - # with_noise_pipes: Whether to use Noise pipes for additional security features - with_noise_pipes=False, + # TODO: add early data ) # Create a security options dictionary mapping protocol ID to transport diff --git a/examples/doc-examples/example_peer_discovery.py b/examples/doc-examples/example_peer_discovery.py index 7ceec3754..b2805576e 100644 --- a/examples/doc-examples/example_peer_discovery.py +++ b/examples/doc-examples/example_peer_discovery.py @@ -31,9 +31,7 @@ async def main(): noise_privkey=key_pair.private_key, # early_data: Optional data to send during the handshake # (None means no early data) - early_data=None, - # with_noise_pipes: Whether to use Noise pipes for additional security features - with_noise_pipes=False, + # TODO: add early data ) # Create a security options dictionary mapping protocol ID to transport diff --git a/examples/doc-examples/example_running.py b/examples/doc-examples/example_running.py index a01699310..6dd6d1bb8 100644 --- a/examples/doc-examples/example_running.py +++ b/examples/doc-examples/example_running.py @@ -28,9 +28,7 @@ async def main(): noise_privkey=key_pair.private_key, # early_data: Optional data to send during the handshake # (None means no early data) - early_data=None, - # with_noise_pipes: Whether to use Noise pipes for additional security features - with_noise_pipes=False, + # TODO: add early data ) # Create a security options dictionary mapping protocol ID to transport diff --git a/libp2p/security/noise/early_data.py b/libp2p/security/noise/early_data.py new file mode 100644 index 000000000..7b3da756d --- /dev/null +++ b/libp2p/security/noise/early_data.py @@ -0,0 +1,68 @@ +from abc import ABC, abstractmethod + +from libp2p.abc import IRawConnection +from libp2p.custom_types import TProtocol +from libp2p.peer.id import ID + +from .pb import noise_pb2 as noise_pb + + +class EarlyDataHandler(ABC): + """Interface for handling early data during Noise handshake""" + + @abstractmethod + async def send( + self, conn: IRawConnection, peer_id: ID + ) -> noise_pb.NoiseExtensions | None: + """Called to generate early data to send during handshake""" + pass + + @abstractmethod + async def received( + self, conn: IRawConnection, extensions: noise_pb.NoiseExtensions | None + ) -> None: + """Called when early data is received during handshake""" + pass + + +class TransportEarlyDataHandler(EarlyDataHandler): + """Default early data handler for muxer negotiation""" + + def __init__(self, supported_muxers: list[TProtocol]): + self.supported_muxers = supported_muxers + self.received_muxers: list[TProtocol] = [] + + async def send( + self, conn: IRawConnection, peer_id: ID + ) -> noise_pb.NoiseExtensions | None: + """Send our supported muxers list""" + if not self.supported_muxers: + return None + + extensions = noise_pb.NoiseExtensions() + # Convert TProtocol to string for serialization + extensions.stream_muxers[:] = [str(muxer) for muxer in self.supported_muxers] + return extensions + + async def received( + self, conn: IRawConnection, extensions: noise_pb.NoiseExtensions | None + ) -> None: + """Store received muxers list""" + if extensions and extensions.stream_muxers: + self.received_muxers = [ + TProtocol(muxer) for muxer in extensions.stream_muxers + ] + + def match_muxers(self, is_initiator: bool) -> TProtocol | None: + """Find first common muxer between local and remote""" + if is_initiator: + # Initiator: find first local muxer that remote supports + for local_muxer in self.supported_muxers: + if local_muxer in self.received_muxers: + return local_muxer + else: + # Responder: find first remote muxer that we support + for remote_muxer in self.received_muxers: + if remote_muxer in self.supported_muxers: + return remote_muxer + return None diff --git a/libp2p/security/noise/patterns.py b/libp2p/security/noise/patterns.py index 00f51d063..d1f6f9345 100644 --- a/libp2p/security/noise/patterns.py +++ b/libp2p/security/noise/patterns.py @@ -30,6 +30,9 @@ SecureSession, ) +from .early_data import ( + EarlyDataHandler, +) from .exceptions import ( HandshakeHasNotFinished, InvalidSignature, @@ -45,6 +48,7 @@ make_handshake_payload_sig, verify_handshake_payload_sig, ) +from .pb import noise_pb2 as noise_pb class IPattern(ABC): @@ -62,7 +66,8 @@ class BasePattern(IPattern): noise_static_key: PrivateKey local_peer: ID libp2p_privkey: PrivateKey - early_data: bytes | None + initiator_early_data_handler: EarlyDataHandler | None + responder_early_data_handler: EarlyDataHandler | None def create_noise_state(self) -> NoiseState: noise_state = NoiseState.from_name(self.protocol_name) @@ -73,11 +78,50 @@ def create_noise_state(self) -> NoiseState: raise NoiseStateError("noise_protocol is not initialized") return noise_state - def make_handshake_payload(self) -> NoiseHandshakePayload: + async def make_handshake_payload( + self, conn: IRawConnection, peer_id: ID, is_initiator: bool + ) -> NoiseHandshakePayload: signature = make_handshake_payload_sig( self.libp2p_privkey, self.noise_static_key.get_public_key() ) - return NoiseHandshakePayload(self.libp2p_privkey.get_public_key(), signature) + + # NEW: Get early data from appropriate handler + extensions = None + if is_initiator and self.initiator_early_data_handler: + extensions = await self.initiator_early_data_handler.send(conn, peer_id) + elif not is_initiator and self.responder_early_data_handler: + extensions = await self.responder_early_data_handler.send(conn, peer_id) + + # NEW: Serialize extensions into early_data field + early_data = None + if extensions: + early_data = extensions.SerializeToString() + + return NoiseHandshakePayload( + self.libp2p_privkey.get_public_key(), + signature, + early_data, # ← This is the key addition + ) + + async def handle_received_payload( + self, conn: IRawConnection, payload: NoiseHandshakePayload, is_initiator: bool + ) -> None: + """Process early data from received payload""" + if not payload.early_data: + return + + # Deserialize the NoiseExtensions from early_data field + try: + extensions = noise_pb.NoiseExtensions.FromString(payload.early_data) + except Exception: + # Invalid extensions, ignore silently + return + + # Pass to appropriate handler + if is_initiator and self.initiator_early_data_handler: + await self.initiator_early_data_handler.received(conn, extensions) + elif not is_initiator and self.responder_early_data_handler: + await self.responder_early_data_handler.received(conn, extensions) class PatternXX(BasePattern): @@ -86,13 +130,15 @@ def __init__( local_peer: ID, libp2p_privkey: PrivateKey, noise_static_key: PrivateKey, - early_data: bytes | None = None, + initiator_early_data_handler: EarlyDataHandler | None, + responder_early_data_handler: EarlyDataHandler | None, ) -> None: self.protocol_name = b"Noise_XX_25519_ChaChaPoly_SHA256" self.local_peer = local_peer self.libp2p_privkey = libp2p_privkey self.noise_static_key = noise_static_key - self.early_data = early_data + self.initiator_early_data_handler = initiator_early_data_handler + self.responder_early_data_handler = responder_early_data_handler async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn: noise_state = self.create_noise_state() @@ -106,18 +152,23 @@ async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn: read_writer = NoiseHandshakeReadWriter(conn, noise_state) - # Consume msg#1. + # 1. Consume msg#1 (just empty bytes) await read_writer.read_msg() - # Send msg#2, which should include our handshake payload. - our_payload = self.make_handshake_payload() + # 2. Send msg#2 with our payload INCLUDING EARLY DATA + our_payload = await self.make_handshake_payload( + conn, + self.local_peer, # We send our own peer ID in responder role + is_initiator=False, + ) msg_2 = our_payload.serialize() await read_writer.write_msg(msg_2) - # Receive and consume msg#3. + # 3. Receive msg#3 msg_3 = await read_writer.read_msg() peer_handshake_payload = NoiseHandshakePayload.deserialize(msg_3) + # Extract remote pubkey from noise handshake state if handshake_state.rs is None: raise NoiseStateError( "something is wrong in the underlying noise `handshake_state`: " @@ -126,14 +177,31 @@ async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn: ) remote_pubkey = self._get_pubkey_from_noise_keypair(handshake_state.rs) + # 4. Verify signature (unchanged) if not verify_handshake_payload_sig(peer_handshake_payload, remote_pubkey): raise InvalidSignature + + # NEW: Process early data from msg#3 AFTER signature verification + await self.handle_received_payload( + conn, peer_handshake_payload, is_initiator=False + ) + remote_peer_id_from_pubkey = ID.from_pubkey(peer_handshake_payload.id_pubkey) if not noise_state.handshake_finished: raise HandshakeHasNotFinished( "handshake is done but it is not marked as finished in `noise_state`" ) + + # NEW: Get negotiated muxer for connection state + # negotiated_muxer = None + if self.responder_early_data_handler and hasattr( + self.responder_early_data_handler, "match_muxers" + ): + # negotiated_muxer = + # self.responder_early_data_handler.match_muxers(is_initiator=False) + pass + transport_read_writer = NoiseTransportReadWriter(conn, noise_state) return SecureSession( local_peer=self.local_peer, @@ -142,6 +210,8 @@ async def handshake_inbound(self, conn: IRawConnection) -> ISecureConn: remote_permanent_pubkey=remote_pubkey, is_initiator=False, conn=transport_read_writer, + # NOTE: negotiated_muxer would need to be added to SecureSession constructor + # For now, store it in connection metadata or similar ) async def handshake_outbound( @@ -158,24 +228,27 @@ async def handshake_outbound( if handshake_state is None: raise NoiseStateError("Handshake state is not initialized") - # Send msg#1, which is *not* encrypted. + # 1. Send msg#1 (empty) - no early data possible in XX pattern msg_1 = b"" await read_writer.write_msg(msg_1) - # Read msg#2 from the remote, which contains the public key of the peer. + # 2. Read msg#2 from responder msg_2 = await read_writer.read_msg() peer_handshake_payload = NoiseHandshakePayload.deserialize(msg_2) + # Extract remote pubkey from noise handshake state if handshake_state.rs is None: raise NoiseStateError( "something is wrong in the underlying noise `handshake_state`: " - "we received and consumed msg#3, which should have included the " + "we received and consumed msg#2, which should have included the " "remote static public key, but it is not present in the handshake_state" ) remote_pubkey = self._get_pubkey_from_noise_keypair(handshake_state.rs) + # Verify signature BEFORE processing early data (security) if not verify_handshake_payload_sig(peer_handshake_payload, remote_pubkey): raise InvalidSignature + remote_peer_id_from_pubkey = ID.from_pubkey(peer_handshake_payload.id_pubkey) if remote_peer_id_from_pubkey != remote_peer: raise PeerIDMismatchesPubkey( @@ -184,8 +257,15 @@ async def handshake_outbound( f"remote_peer_id_from_pubkey={remote_peer_id_from_pubkey}" ) - # Send msg#3, which includes our encrypted payload and our noise static key. - our_payload = self.make_handshake_payload() + # NEW: Process early data from msg#2 AFTER verification + await self.handle_received_payload( + conn, peer_handshake_payload, is_initiator=True + ) + + # 3. Send msg#3 with our payload INCLUDING EARLY DATA + our_payload = await self.make_handshake_payload( + conn, remote_peer, is_initiator=True + ) msg_3 = our_payload.serialize() await read_writer.write_msg(msg_3) @@ -193,6 +273,16 @@ async def handshake_outbound( raise HandshakeHasNotFinished( "handshake is done but it is not marked as finished in `noise_state`" ) + + # NEW: Get negotiated muxer + # negotiated_muxer = None + if self.initiator_early_data_handler and hasattr( + self.initiator_early_data_handler, "match_muxers" + ): + pass + # negotiated_muxer = + # self.initiator_early_data_handler.match_muxers(is_initiator=True) + transport_read_writer = NoiseTransportReadWriter(conn, noise_state) return SecureSession( local_peer=self.local_peer, @@ -201,6 +291,8 @@ async def handshake_outbound( remote_permanent_pubkey=remote_pubkey, is_initiator=True, conn=transport_read_writer, + # NOTE: negotiated_muxer would need to be added to SecureSession constructor + # For now, store it in connection metadata or similar ) @staticmethod diff --git a/libp2p/security/noise/pb/noise.proto b/libp2p/security/noise/pb/noise.proto index 05a78c6f3..f818fe2e2 100644 --- a/libp2p/security/noise/pb/noise.proto +++ b/libp2p/security/noise/pb/noise.proto @@ -1,8 +1,12 @@ -syntax = "proto3"; +syntax = "proto2"; package pb; +message NoiseExtensions { + repeated string stream_muxers = 2; +} + message NoiseHandshakePayload { - bytes identity_key = 1; - bytes identity_sig = 2; - bytes data = 3; + optional bytes identity_key = 1; + optional bytes identity_sig = 2; + optional bytes data = 3; } diff --git a/libp2p/security/noise/pb/noise_pb2.py b/libp2p/security/noise/pb/noise_pb2.py index dd078b0f4..d602719a3 100644 --- a/libp2p/security/noise/pb/noise_pb2.py +++ b/libp2p/security/noise/pb/noise_pb2.py @@ -13,13 +13,15 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$libp2p/security/noise/pb/noise.proto\x12\x02pb\"Q\n\x15NoiseHandshakePayload\x12\x14\n\x0cidentity_key\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_sig\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$libp2p/security/noise/pb/noise.proto\x12\x02pb\"(\n\x0fNoiseExtensions\x12\x15\n\rstream_muxers\x18\x02 \x03(\t\"Q\n\x15NoiseHandshakePayload\x12\x14\n\x0cidentity_key\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_sig\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'libp2p.security.noise.pb.noise_pb2', globals()) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _NOISEHANDSHAKEPAYLOAD._serialized_start=44 - _NOISEHANDSHAKEPAYLOAD._serialized_end=125 + _NOISEEXTENSIONS._serialized_start=44 + _NOISEEXTENSIONS._serialized_end=84 + _NOISEHANDSHAKEPAYLOAD._serialized_start=86 + _NOISEHANDSHAKEPAYLOAD._serialized_end=167 # @@protoc_insertion_point(module_scope) diff --git a/libp2p/security/noise/pb/noise_pb2.pyi b/libp2p/security/noise/pb/noise_pb2.pyi index f0f3201ef..cc1e1c34b 100644 --- a/libp2p/security/noise/pb/noise_pb2.pyi +++ b/libp2p/security/noise/pb/noise_pb2.pyi @@ -4,12 +4,30 @@ isort:skip_file """ import builtins +import collections.abc import google.protobuf.descriptor +import google.protobuf.internal.containers import google.protobuf.message import typing DESCRIPTOR: google.protobuf.descriptor.FileDescriptor +@typing.final +class NoiseExtensions(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STREAM_MUXERS_FIELD_NUMBER: builtins.int + @property + def stream_muxers(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... + def __init__( + self, + *, + stream_muxers: collections.abc.Iterable[builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing.Literal["stream_muxers", b"stream_muxers"]) -> None: ... + +global___NoiseExtensions = NoiseExtensions + @typing.final class NoiseHandshakePayload(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor @@ -23,10 +41,11 @@ class NoiseHandshakePayload(google.protobuf.message.Message): def __init__( self, *, - identity_key: builtins.bytes = ..., - identity_sig: builtins.bytes = ..., - data: builtins.bytes = ..., + identity_key: builtins.bytes | None = ..., + identity_sig: builtins.bytes | None = ..., + data: builtins.bytes | None = ..., ) -> None: ... + def HasField(self, field_name: typing.Literal["data", b"data", "identity_key", b"identity_key", "identity_sig", b"identity_sig"]) -> builtins.bool: ... def ClearField(self, field_name: typing.Literal["data", b"data", "identity_key", b"identity_key", "identity_sig", b"identity_sig"]) -> None: ... global___NoiseHandshakePayload = NoiseHandshakePayload diff --git a/libp2p/security/noise/transport.py b/libp2p/security/noise/transport.py index b26e06447..e9d0bcfd4 100644 --- a/libp2p/security/noise/transport.py +++ b/libp2p/security/noise/transport.py @@ -14,6 +14,7 @@ ID, ) +from .early_data import EarlyDataHandler, TransportEarlyDataHandler from .patterns import ( IPattern, PatternXX, @@ -26,35 +27,40 @@ class Transport(ISecureTransport): libp2p_privkey: PrivateKey noise_privkey: PrivateKey local_peer: ID - early_data: bytes | None - with_noise_pipes: bool + supported_muxers: list[TProtocol] + initiator_early_data_handler: EarlyDataHandler | None + responder_early_data_handler: EarlyDataHandler | None def __init__( self, libp2p_keypair: KeyPair, noise_privkey: PrivateKey, - early_data: bytes | None = None, - with_noise_pipes: bool = False, + supported_muxers: list[TProtocol] | None = None, + initiator_handler: EarlyDataHandler | None = None, + responder_handler: EarlyDataHandler | None = None, ) -> None: self.libp2p_privkey = libp2p_keypair.private_key self.noise_privkey = noise_privkey self.local_peer = ID.from_pubkey(libp2p_keypair.public_key) - self.early_data = early_data - self.with_noise_pipes = with_noise_pipes + self.supported_muxers = supported_muxers or [] - if self.with_noise_pipes: - raise NotImplementedError + # Create default handlers for muxer negotiation if none provided + if initiator_handler is None and self.supported_muxers: + initiator_handler = TransportEarlyDataHandler(self.supported_muxers) + if responder_handler is None and self.supported_muxers: + responder_handler = TransportEarlyDataHandler(self.supported_muxers) + + self.initiator_early_data_handler = initiator_handler + self.responder_early_data_handler = responder_handler def get_pattern(self) -> IPattern: - if self.with_noise_pipes: - raise NotImplementedError - else: - return PatternXX( - self.local_peer, - self.libp2p_privkey, - self.noise_privkey, - self.early_data, - ) + return PatternXX( + self.local_peer, + self.libp2p_privkey, + self.noise_privkey, + self.initiator_early_data_handler, + self.responder_early_data_handler, + ) async def secure_inbound(self, conn: IRawConnection) -> ISecureConn: pattern = self.get_pattern() diff --git a/tests/utils/factories.py b/tests/utils/factories.py index 75639e369..584f0975e 100644 --- a/tests/utils/factories.py +++ b/tests/utils/factories.py @@ -173,8 +173,7 @@ def noise_transport_factory(key_pair: KeyPair) -> ISecureTransport: return NoiseTransport( libp2p_keypair=key_pair, noise_privkey=noise_static_key_factory(), - early_data=None, - with_noise_pipes=False, + # TODO: add early data ) From cacb3c8aca3d760ea11dc5ebe3469929f0bd9d6d Mon Sep 17 00:00:00 2001 From: varun-r-mallya Date: Tue, 26 Aug 2025 12:48:57 +0530 Subject: [PATCH 2/2] feat: add webtransport certhashes field to NoiseExtensions and implement serialization test Signed-off-by: varun-r-mallya --- examples/doc-examples/example_encryption_noise.py | 3 --- libp2p/security/noise/pb/noise.proto | 1 + libp2p/security/noise/pb/noise_pb2.py | 8 ++++---- libp2p/security/noise/pb/noise_pb2.pyi | 6 +++++- .../security/noise/test_noise_extension_protobuf.py | 13 +++++++++++++ 5 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 tests/core/security/noise/test_noise_extension_protobuf.py diff --git a/examples/doc-examples/example_encryption_noise.py b/examples/doc-examples/example_encryption_noise.py index 31d80580f..f78a5ec44 100644 --- a/examples/doc-examples/example_encryption_noise.py +++ b/examples/doc-examples/example_encryption_noise.py @@ -24,10 +24,7 @@ async def main(): noise_transport = NoiseTransport( # local_key_pair: The key pair used for libp2p identity and authentication libp2p_keypair=key_pair, - # noise_privkey: The private key used for Noise protocol encryption noise_privkey=key_pair.private_key, - # early_data: Optional data to send during the handshake - # (None means no early data) # TODO: add early data ) diff --git a/libp2p/security/noise/pb/noise.proto b/libp2p/security/noise/pb/noise.proto index f818fe2e2..bd6b67a6c 100644 --- a/libp2p/security/noise/pb/noise.proto +++ b/libp2p/security/noise/pb/noise.proto @@ -2,6 +2,7 @@ syntax = "proto2"; package pb; message NoiseExtensions { + repeated bytes webtransport_certhashes = 1; repeated string stream_muxers = 2; } diff --git a/libp2p/security/noise/pb/noise_pb2.py b/libp2p/security/noise/pb/noise_pb2.py index d602719a3..48a3ef61f 100644 --- a/libp2p/security/noise/pb/noise_pb2.py +++ b/libp2p/security/noise/pb/noise_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$libp2p/security/noise/pb/noise.proto\x12\x02pb\"(\n\x0fNoiseExtensions\x12\x15\n\rstream_muxers\x18\x02 \x03(\t\"Q\n\x15NoiseHandshakePayload\x12\x14\n\x0cidentity_key\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_sig\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$libp2p/security/noise/pb/noise.proto\x12\x02pb\"I\n\x0fNoiseExtensions\x12\x1f\n\x17webtransport_certhashes\x18\x01 \x03(\x0c\x12\x15\n\rstream_muxers\x18\x02 \x03(\t\"Q\n\x15NoiseHandshakePayload\x12\x14\n\x0cidentity_key\x18\x01 \x01(\x0c\x12\x14\n\x0cidentity_sig\x18\x02 \x01(\x0c\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'libp2p.security.noise.pb.noise_pb2', globals()) @@ -21,7 +21,7 @@ DESCRIPTOR._options = None _NOISEEXTENSIONS._serialized_start=44 - _NOISEEXTENSIONS._serialized_end=84 - _NOISEHANDSHAKEPAYLOAD._serialized_start=86 - _NOISEHANDSHAKEPAYLOAD._serialized_end=167 + _NOISEEXTENSIONS._serialized_end=117 + _NOISEHANDSHAKEPAYLOAD._serialized_start=119 + _NOISEHANDSHAKEPAYLOAD._serialized_end=200 # @@protoc_insertion_point(module_scope) diff --git a/libp2p/security/noise/pb/noise_pb2.pyi b/libp2p/security/noise/pb/noise_pb2.pyi index cc1e1c34b..0265e983a 100644 --- a/libp2p/security/noise/pb/noise_pb2.pyi +++ b/libp2p/security/noise/pb/noise_pb2.pyi @@ -16,15 +16,19 @@ DESCRIPTOR: google.protobuf.descriptor.FileDescriptor class NoiseExtensions(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor + WEBTRANSPORT_CERTHASHES_FIELD_NUMBER: builtins.int STREAM_MUXERS_FIELD_NUMBER: builtins.int @property + def webtransport_certhashes(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.bytes]: ... + @property def stream_muxers(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... def __init__( self, *, + webtransport_certhashes: collections.abc.Iterable[builtins.bytes] | None = ..., stream_muxers: collections.abc.Iterable[builtins.str] | None = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["stream_muxers", b"stream_muxers"]) -> None: ... + def ClearField(self, field_name: typing.Literal["stream_muxers", b"stream_muxers", "webtransport_certhashes", b"webtransport_certhashes"]) -> None: ... global___NoiseExtensions = NoiseExtensions diff --git a/tests/core/security/noise/test_noise_extension_protobuf.py b/tests/core/security/noise/test_noise_extension_protobuf.py new file mode 100644 index 000000000..a7e64e817 --- /dev/null +++ b/tests/core/security/noise/test_noise_extension_protobuf.py @@ -0,0 +1,13 @@ +from libp2p.security.noise.pb import noise_pb2 as noise_pb + + +def test_noise_extensions_serialization(): + # Test NoiseExtensions + ext = noise_pb.NoiseExtensions() + ext.stream_muxers.append("/mplex/6.7.0") + ext.stream_muxers.append("/yamux/1.0.0") + + # Serialize and deserialize + data = ext.SerializeToString() + ext2 = noise_pb.NoiseExtensions.FromString(data) + assert list(ext2.stream_muxers) == ["/mplex/6.7.0", "/yamux/1.0.0"]