mirror of
https://github.com/varun-r-mallya/py-libp2p.git
synced 2025-12-31 20:36:24 +00:00
Add early data support to Noise protocol
Signed-off-by: varun-r-mallya <varunrmallya@gmail.com>
This commit is contained in:
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
68
libp2p/security/noise/early_data.py
Normal file
68
libp2p/security/noise/early_data.py
Normal file
@ -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
|
||||
@ -30,6 +30,9 @@ from libp2p.security.secure_session import (
|
||||
SecureSession,
|
||||
)
|
||||
|
||||
from .early_data import (
|
||||
EarlyDataHandler,
|
||||
)
|
||||
from .exceptions import (
|
||||
HandshakeHasNotFinished,
|
||||
InvalidSignature,
|
||||
@ -45,6 +48,7 @@ from .messages import (
|
||||
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 @@ class BasePattern(IPattern):
|
||||
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 @@ class PatternXX(BasePattern):
|
||||
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 @@ class PatternXX(BasePattern):
|
||||
|
||||
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 @@ class PatternXX(BasePattern):
|
||||
)
|
||||
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 @@ class PatternXX(BasePattern):
|
||||
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 @@ class PatternXX(BasePattern):
|
||||
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 @@ class PatternXX(BasePattern):
|
||||
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 @@ class PatternXX(BasePattern):
|
||||
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 @@ class PatternXX(BasePattern):
|
||||
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
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
syntax = "proto3";
|
||||
syntax = "proto2";
|
||||
package pb;
|
||||
|
||||
message NoiseHandshakePayload {
|
||||
bytes identity_key = 1;
|
||||
bytes identity_sig = 2;
|
||||
bytes data = 3;
|
||||
message NoiseExtensions {
|
||||
repeated string stream_muxers = 2;
|
||||
}
|
||||
|
||||
message NoiseHandshakePayload {
|
||||
optional bytes identity_key = 1;
|
||||
optional bytes identity_sig = 2;
|
||||
optional bytes data = 3;
|
||||
}
|
||||
|
||||
@ -13,13 +13,15 @@ _sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -14,6 +14,7 @@ from libp2p.peer.id import (
|
||||
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()
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user