ECNet2 is an encrypted networking library for CC:Tweaked. You can find usage examples in the examples directory.
- CCryptolib >=1.1.0 (you still need to initialize the random generator yourself)
- Let the user manage the lifetime of connections.
- Minimize the work done by the responder before they accept a handshake.
- Let the user pick a "protocol" (channel, namespace, ...) to talk through.
- Strong assurances for traffic authenticity and confidentiality.
- High efficiency in both computation and bandwidth usage.
- Best-effort hiding of relevant parties' identities.
- Best-effort handling of network abuse.
- Authenticated multicast or broadcast of messages.
Opens a modem for communications.
Closes a modem for communications.
Returns whether a modem is open for communications.
Returns the address for connecting to this device.
Function used for managing listener and connection events. Intended to be put in parallel with users code.
Creates a protocol from a given interface.
A table containing a description for a protocol.
The protocol's name.
A serializer for protocol objects.
A deserializer for protocol objects.
A namespace for interpreting messages received over connections.
Creates a new connection using this protocol and a modem.
Creates a listener for this protocol on all open modems.
A listener for incoming connection requests.
The listener's ID, used in resolving ecnet2_request
events.
Accepts a request and builds a connection. Waits for the next request if none are provided.
Throws "invalid listener for this request"
if the supplied request isn't meant
for this listener.
Returns a dummy connection if the request is malformed, or if the request has already been accepted.
An encrypted tunnel operating over a network.
The connection's ID, used in ecnet2_message
events.
Sends a message.
Throws "can't send on an incomplete connection"
until at least one
message has been received.
Yields until a message is received. Returns the sender and contents, or nil on timeout.
A connection request.
listenerId
- Theid
field of the listener that received this request.request
- The request to pass on to theaccept
method.side
- Which modem the request was received through.channel
- The channel the request was sent on.distance
- Distance of the sender.
A message in a connection.
connectionId
- Theid
field of the connection that received this message.sender
- The sender's address.message
- The deserialized message data.channel
- The channel the message was sent on.distance
- Distance of the sender.
Every ECNet2 packet has a 32 byte prefix known as the descriptor. Descriptors allow the receiver to know whether it is supposed to process a packet. Furthermore, secret descriptors allow for some resistance against decryption failure denial-of-service attacks on networks with no wormholes.
The listener descriptor is defined as BLAKE3(BLAKE3(pk .. BLAKE3(protocol)))
,
where pk
is the listener's public key and protocol
is the protocol name.
The connection descriptors are derived from the current decryption key, which is ratcheted every time a new message is received.
We use the noise XK handshake, its pattern is:
XK:
<- s
...
-> e, es
<- e, ee
-> s, se
The contents of each handshake payload are:
This payload currently contains only padding.
This payload contains a user-defined reply and padding. Neither the user nor ECNet know who the initiator is. As a result, naive assumptions match exactly what the payload security properties (2, 1) are.
This payload contains a user-defined message and padding. The naive assumptions match exactly what the payload security properties (2, 5) are.
We need the initiator to have a secret descriptor at the first response, otherwise an attacker could trigger decryption failures arbitrarily, throwing the entire connection away. We could try restarting the connection again, but that's difficult to model in the interface.
- The initiator's identity claim is vulnerable to replay attack, so we can't assume anything until their first transport message, making IK pointless.
- IK has an
ss
token, which is harder to protect against timing attacks on the result of the DH operation, whilees
andse
are a bit safer. - IK hides identity more poorly than XK.
Authenticating the initiatior makes the API simpler from the user's point of view, since they don't have to handle whether the message has a sender or not.
The message size limit is 2¹⁵ - 1 = 32767 bytes. The other half of the payload is reserved for ECNet metadata.
The message size limit is 2¹⁵ - 1 = 32767 bytes. The other half of the payload is reserved for ECNet metadata.
50 bytes of overhead:
- 32 bytes for the descriptor
- 1 byte for packet type information
- At least 1 byte for padding
- 16 bytes for the message's tag
Since noise allows packets of at most 2¹⁶ - 1 bytes in length, the message size limit is 2¹⁶ - 1 - 50 = 65485 bytes.
The XK handshake is modeled into Connection
and Listener
objects. The second
payload is modeled as a reply
parameter to accept
, while the third payload
is modeled as a regular message:
Handshake payloads:
connect() -> e, es, "" -> os.pullEvent("ecnet2_request")
receive() <- e, ee, reply <- accept(reply)
send(msg) -> s, se, msg -> receive()
Transport:
receive() <- msg <- send(msg)
send(msg) -> msg -> receive()