Æthernet Architecture and Stateless Protocol

Æthernet is a cloud connectivity architecture for IoT devices and connected applications. Its stateless protocol removes session handshakes.

Each client uses a dynamically assigned Personal Cloud of geographically distributed relay servers to reduce latency and recover from packet loss, network changes, or endpoint failures.

This page explains the transport layer, per-request authentication, hot-swap routing, self-provisioning, and client-side connection logic.

Architecture

Æthernet is built on three core architectural principles:

  • No Handshakes Protocol. Once a client is registered, it can send a request to a server in a single message containing the information required for encryption, client authentication, and the request payload.

  • Multiple simultaneous endpoints. A client can send requests to any server in its Personal Cloud at any time, including simultaneous requests to multiple servers.

  • Thick client. The client can detect and respond to connectivity problems that are visible only from the client side, improving connection stability.

Message delivery latency

A conventional message broker can provide low delivery latency while the Internet connection is stable. Latency rises, or connectivity may fail entirely, when the connection degrades or an endpoint stops responding:

  • A connection with a high packet-loss rate may never complete the broker handshake. Losing any packet restarts the connection process, and protocols with multiple handshake steps may be unable to finish under severe loss.

    • Æthernet avoids this failure mode with its No Handshakes Protocol and zero-round-trip request flow.

  • If a cloud server responds slowly or becomes unavailable, a conventional client closes the connection and attempts another endpoint, which can add seconds of delay.

    • Æthernet accepts duplicate requests sent in parallel. If the first server does not reply within a few milliseconds beyond the expected network round-trip time, the client can send the same request to another server without waiting for a connection timeout.

Identity

Every Æthernet client is identified by a unique 16-byte identifier (UID) assigned during registration or self-provisioning. The UID is permanent, non-public, and non-secret, and Æthernet uses it to authenticate requests.

The UID also serves as the client address for message delivery and application-server management. A new client registers under a parent client, usually an application server, which is also addressed by its own UID.

Client-server architecture

Æthernet consists of independent servers distributed across the globe. The servers do not connect directly to one another; instead, they cache the data they need from the central database.

Each client receives a Personal Cloud, which is a subset of the full Æthernet cloud. Depending on the use case, the client can send requests sequentially or simultaneously, including multiple concurrent requests to the same server.

Æthernet configures and can update this Personal Cloud to minimize latency during normal operation and maintain connectivity when conditions degrade. The first server in the cloud list is the closest landing point for the client and is selected to provide the lowest message-delivery latency.

Æthernet geo-distributed client and server architecture

Application server

An application server is a regular Æthernet client controlled by the developer. It manages child clients by allocating quotas, granting message permissions, and blocking or deleting clients.

Unlike backends that expose separate REST or gRPC management APIs, Æthernet manages every client through its parent. The application server is notified when a child is registered or approaches its allocated quota.

The application server can therefore focus on application business logic while Æthernet handles infrastructure concerns such as scaling, load balancing, availability, attack resilience, and public IP addressing.

Application Server

Æthernet uses a tree structure rooted at Æther, which can manage child clients at any depth.

Dev is a helper application hosted by Æthernet that allocates trial quotas to developers. When a user starts the chat example, a new client is created and registered.

The hosted chat application server receives the registration notification, allocates quota, and configures message permissions between the new client and the other chat clients. When the client reaches its quota limit, the chat server deletes it.

Users is the namespace for website user accounts.

Anonymous is where all self-provisioned applications are hosted.

A more complex hierarchy with Mobile, IoT, and Infra nodes is shown above as an example.

  • Admin is a node associated with an account on aethernet.io.

  • Mobile is the application server that manages smartphone application instances.

  • IoT is a separate node that manages all IoT devices.

  • Infra is a node for service functions such as storing data in the backend database.

Client lifecycle

A client is the addressable entity in Æthernet. Every client has a parent and may have multiple children.

Æthernet uses two separate clouds:

  • The registration cloud is resolved through register.aethernet.io and returns a list of servers that expose only the registration API. A new client uses this cloud to become permanently registered.

  • The working cloud accepts requests only from registered clients. Server endpoint IP addresses are provided during registration or can be obtained from cloud.aethernet.io.

Registering a new client

A device or application becomes an Æthernet client after it is registered in the central database. At the end of registration, the client receives:

  • A permanent unique identifier (UID). It is non-public and non-secret, and is shared only with the clients that need to communicate with it.

  • A permanent master key. It is stored only in the central database and is used to derive keys for all servers in the client’s Personal Cloud.

  • A Personal Cloud. This is a list of servers selected by Æthernet to minimize response latency and maximize client connectivity.

At minimum, a client is represented by its UID and master key. It can then send requests to Æthernet servers, including pulling or sending messages, checking online status, and requesting its remaining quota.

Registration is often referred to as provisioning, or as just-in-time provisioning when the client registers itself.

Working cloud

A client connects to servers in its Personal Cloud and sends requests. Connections may be long-lived or gracefully closed by either the client or an Æthernet server.

The client pulls messages through heartbeat requests and can receive server notifications about new messages, completed requests, newly registered children, or cloud reconfiguration.

Æthernet can reconfigure a client’s Personal Cloud whenever the client is online. Reasons include load balancing, movement to a closer server, server failure or planned shutdown, and deployment of a new server.

All interactions with Æthernet servers use messages created by Actions, the asynchronous operations exposed by the Æther API.

Action

An Action is an object that represents an asynchronous process for sending and receiving data through Æthernet messages.

A newly created Action is added to the client’s Action list and executed periodically. It can send requests to registration-cloud or working-cloud servers and is notified when a server request or response arrives. A single request may contain several messages.

The action inspects messages and performs some operations:

  • changes the state of Æther, a client, or a cloud, for example by adding servers to the cloud;

  • releases itself or other Actions;

  • creates other Actions.

Message

A message contains parameters for a function executed asynchronously on a remote endpoint. A client pushes the message to the Æthernet cloud, and the server can return one or more messages in reply.

Each message carries authentication information and encrypted payload data, so no preliminary round trips are required.

Infrastructure

The central database stores information about Æthernet clients. When a client is registered, a new record is created. Its most important fields are:

  • UID — a unique, non-public, non-secret 16-byte client identifier. Æthernet does not require this value to follow the UUID format.

  • Master key — a key for the symmetric algorithm supported by the client.

  • Cryptographic algorithms supported by the client.

These values are permanently stored and never changed.

Æthernet uses libsodium, libhydrogen, or a combination of both for cryptographic functions, and bcrypt for client registration. Future releases will support Ascon with a 12-byte nonce and an 8-byte AEAD tag.

Æthernet frequently reconfigures each cloud by adding or removing servers in data centers around the world. The cloud is balanced to minimize message-delivery latency and maintenance cost while maximizing fault tolerance and resilience against attacks.

The registration and working clouds run on separate physical servers. This allows already registered clients to continue operating if the registration cloud becomes unavailable.

Registration

An application can create a new client and receive {uid, masterkey} in response. It can also request a Personal Cloud for the region where the client will operate, placing the client near the closest data centers.

A client can also be created through self-provisioning, in which the registering client completes the registration flow itself. The process is unattended by the application and relies entirely on dedicated registration servers in the Æthernet infrastructure.

The registration cloud is geographically distributed so it can be reached from any client location. Server addresses are resolved through registration.aethernet.io. A client can use any returned IP address or the first round-robin address. Registration servers accept only registration requests.

The self-provisioning flow has three steps. Every request is stateless and optimized for performance: the server keeps no state between requests, improving resistance to denial-of-service attacks.

Requesting public key

The verified public key for asymmetric encryption is obtained at this step. The key is used later to encrypt sensitive data and to transfer it to the registration server.

Requesting Public Key

Æthernet does not use certificates to establish session keys, so key binding is used to prevent man-in-the-middle attacks.

Compromise of a bound public key would require every client to be updated with a replacement key, which may be difficult or impossible for deployed IoT devices.

To reduce this risk, Æthernet uses a global signing public key that clients can bind to. When a registration server joins the registration cloud, it receives data specific to that server:

  • a secret key for asymmetric encryption;

  • a public key for asymmetric encryption, transmitted to registering clients;

  • a public-key signature generated with the global Æthernet public and secret signing keys.

Compromising one registration server does not affect the others because each server uses its own key pair.

The database contains a pool of [Sk, Pk, Pk_sign] tuples. When a registration server is created, one tuple is transferred to it and removed from the pool.

The database does not store the global signing secret key. An external tool is used to refill the pool of signed public keys.

Each time the tool runs, the private signing key is entered manually. The generated keys are saved to a flash drive and then transferred to the database. The tool runs on dedicated offline hardware, and the signing master key is never stored electronically.

The registering client requests a public key for the encryption and signature methods it supports. The request is 5 bytes and the reply is 99 bytes.

The operation is lightweight enough to implement at the network-interface layer. UDP amplification may be a concern; UDP support is not yet implemented.

Requesting proof-of-work parameters

Each newly registered client incurs a charge to the application account. To prevent attackers from draining that account or causing denial of service with large numbers of dummy clients, Æthernet requires proof of work from the registering client.

The request for these parameters contains:

  • Parent UID. The UID of the application under which the client wants to be registered.

  • Proof-of-work method type. Currently, only bcrypt is supported.

  • Symmetric-encryption method type. The method supported by the client for decrypting the server reply.

  • Secret key for symmetric encryption. The server uses this key to encrypt its reply.

  • Public-key and signature method types. The server returns an asymmetric-encryption public key and a signature for that key. The client uses the key to encrypt its next message.

    As with the previous request, the protocol remains stateless: the server does not retain parameters from earlier requests, so the client includes them again.

Retaining pre-registration session state would increase memory use and expose the service to denial-of-service attacks because no UID exists yet to bind that state to a known client.

A man-in-the-middle attacker cannot tamper with the request because the client obtained a verified server public key in the previous step. The reply is encrypted with the client’s secret key and cannot be modified without detection.

Register a client with “proof-of-work”

Æthernet uses proof of work to mitigate Sybil attacks based on excessive registration of dummy clients. Because each self-provisioned client is billed to the application account, the same mechanism also limits malicious account draining.

An application that is allowed to register child clients can define the maximum registration rate for those clients. The rate is measured as total registrations per second across all registration servers.

When the actual rate exceeds the configured maximum, Æthernet increases the proof-of-work factor so each new client must spend more time computing its proof. When the rate falls, Æthernet reduces the factor, but never below the minimum configured for the application.

Setting the minimum too low would let an attacker periodically obtain cheap registrations. The central database aggregates registration rates from all registration servers and distributes the current work-factor value for each application.

The previous request returns the following bcrypt-specific parameters:

Salt — a bcrypt-specific salt containing both the work factor and the salt value. Æthernet generates a unique salt for each registration request.

Because the protocol is stateless, the server does not retain an association between the salt and the registering client. The client returns the salt in the next request so the registration server can verify the completed work.

The client cannot substitute a locally generated salt because the password-suffix field is derived from the salt returned by the server.

Password suffix is a string value concatenated with the numeric password before hashing; bcrypt accepts text passwords only. The server treats this value as opaque and uses it to verify that the client performed the required work. The implementation may change without notice.

The server uses libsodium for random-number generation, cryptographic hashing, and symmetric encryption. The client library returns the bcrypt salt and password suffix unchanged so the server can verify the proof:

  1. decode the suffix from Base64 and decrypt it with the server’s secret key;

    • The request carries 12 nonce bytes, while the remaining 12 bytes are fixed to zero. This is acceptable because the encrypted value is used only for proof-of-work verification rather than to protect secret data, so nonce collisions are not critical in this context.

    • bcrypt accepts passwords up to 72 characters. A 24-byte nonce plus ciphertext would produce a 74-character Base64 string.

      The suffix therefore uses a 12-byte nonce and totals 56 characters. The uint32 password requires at most 10 decimal characters, keeping the combined value at 66 characters.

    • each server uses a random secret key, so reusing the proof on another server fails;

    • Tampering with the nonce or ciphertext causes verification to fail.

    • integrity verification is lightweight, so the CPU does not become a bottleneck.

  2. check the decrypted timestamp;

    • if the timestamp is older than approximately 10 seconds, reject the request;

    • if the timestamp is within the accepted interval, hash the request and look for that hash in the table of recent requests;

      • if the value is not found, ignore the request;

      • if the value is found, remove it from the table to prevent replay attacks;

      • the server automatically removes hashes older than the accepted interval. The table requires little storage, and the hash function is intentionally fast rather than cryptographically secure.

    • Checking the server timestamp prevents an attacker from precomputing proofs at a low work factor with power-efficient but slow hardware. The server binds the salt to the password suffix, making old precomputed proofs unusable.

  3. compute a hash of the salt and parent UID to verify that:

    • the bcrypt salt contains the correct work factor and was not substituted by the client;

    • the parent UID matches the application for which the proof of work was computed. This prevents a client from using a fake application configured with weaker proof-of-work parameters.

The registration server does not expose useful timing information because it silently discards invalid requests.

The proof of work is represented by a four-byte number. The server converts it to a string and concatenates it with the password suffix, then passes the result and the bcrypt salt to the bcrypt hash function.

The output is reduced with CRC32 to a four-byte integer. That integer must be less than or equal to the maximum hash value, which Æthernet adjusts to support the target client-registration rate.

The configured average proof-of-work time does not mean every client takes exactly that long, because the search time follows a probability distribution. A proof-of-work pool reduces this variance, and the client must use the pool size specified by the server.

The server may return updated PoW parameters if they changed after the previous request. This prevents a delayed-computation attack in which an attacker accumulates requests while the PoW factor is low and submits the computed proofs later.

The adjustment is based on successful registrations rather than requests for PoW parameters. Otherwise, an attacker could raise the victim application’s PoW factor until legitimate clients could no longer register.

At the end of registration, the client specifies the supported symmetric-cryptography method and the corresponding secret key. The client encrypts this key with the Æthernet service database’s asymmetric public key, which is delivered together with a signature.

The client verifies the signature before transmitting the master key. As a result, compromise of a registration server does not expose the client’s master key.

The proof-of-work response contains:

  • UID;

  • an ephemeral UID;

  • the working cloud.

Proof-of-work hashing algorithms

A proof-of-work hashing algorithm should:

  • have a small enough code footprint for constrained MCUs;

  • use little enough RAM for constrained MCUs;

  • complete a single iteration quickly enough for a registration server to verify proofs efficiently;

  • resist GPU and ASIC acceleration;

  • have no known alternative algorithm that substantially accelerates computation;

  • provide appropriate cryptographic resistance.

CRC and MurmurHash are very fast but non-cryptographic, so they do not meet the proof-of-work requirements.

SHA-family hashes are heavily accelerated by GPUs and ASICs.

Homomorphic encryption was evaluated, but published reports described a 75× acceleration, an overall improvement of 100 million times over three years, and a 50× GPU acceleration factor.

Password hashing algorithms compared with bcrypt:

  • Argon2 — the best-known alternative:

    • has a large and complex code footprint. It is not implemented in the referenced STM32 crypto library, while bcrypt is;

    • requires tens or hundreds of megabytes of RAM even for an approximately 100 ms iteration, with memory use increasing for longer iterations;

    • is weaker than bcrypt for iteration times below one second. A one-second desktop iteration is too slow for MCUs and for server-side verification.

  • scrypt

    • requires a large amount of RAM to resist GPU and ASIC acceleration.

  • bcrypt

    • offers a range of work factors for balancing iteration time and cryptographic resistance;

    • uses 4 KB of RAM;

    • has a mature Unix-derived implementation;

    • CPU vs GPU vs ASIC:

      • example throughput: CPU — 50 K#/s, GPU — 75 K#/s, ASIC — 1 M#/s;

      • GPU acceleration is available, but the cost per iteration is similar to CPU execution;

      • ASIC acceleration is available, but the cost per iteration is close to CPU execution.

  • Balloon hashing is not yet proven, but requires less memory than Argon2.

Bandwidth

The registration flow uses three request-response exchanges. For libsodium, the TCP payload sizes are:

ExchangeSend, bytesReceive, bytes
Request keys599
Request PoW params105216
Register288140

The total is 853 bytes. By comparison, a non-cached TLS session requires approximately 6.5 KB of transferred data.

Working cloud

The working cloud accepts only authenticated requests from registered clients. Each server receives a monotonically increasing number. If a physical server is shut down and restarted, it rejoins the cloud with a new number.

Before a server is added to a client’s cloud, it caches the required central-database fields for the most active clients. A client’s master key remains only in the central database and is never transferred to cloud servers.

Instead, the symmetric key for a specific cloud server is derived as key = KDF(masterkey, server_id | key_index). Initially, key_index = 0.

If a server is compromised, Æthernet can shut down that server without affecting the keys used by other servers.

A client can renew the key on a particular server at any time by incrementing key_index, providing forward secrecy.

Rx (receive) and Tx (transmit) secret keys are derived from a single master key to improve security if either the server or the client is compromised.

Protocol

Æthernet uses a versioned, multilevel encapsulating binary protocol. Its basic unit is a Message whose data contains parameters for a function executed remotely on a server, or data that a server pushes to a client.

The protocol supports request-response exchanges and one-way messages. When a connection is established, a server can also send push messages to the client.

The transport can run over datagrams or streams and may use connection-oriented or connectionless protocols. For a stream transport, each message carries its size, and one message can encapsulate the data of other messages.

Message

Each message starts with a variable-length identifier. Message IDs are organized into namespaces, and the receiver begins deserialization in the root namespace. After the full message, including all nested messages, has been deserialized, the next message begins. Messages are grouped into packets.

Æthernet limits a single packet to 1,500 bytes to avoid fragmentation across common Internet paths. The C++ client library allows a lower limit when another physical medium requires it.

When the decoder reads a message ID from the stream, it creates an instance of the corresponding message class and initializes its data members during deserialization. If the message contains encapsulated messages, deserialization continues through the nested data.

A message may also transform its data, for example by decrypting it. Root message IDs use a namespace separate from nested message IDs: for example, ID #3 may represent a libsodium-encrypted message in the root namespace and a NumMessages message in a subsequent namespace.

A message can encapsulate multiple distinct messages. Example structure:

  • 0: LibsodiumEncrypted: nonce

    • 1: Response: id

      • 1: SetCloud: servers list

      • Debug information: timings

    • 1: Compressed — a proxy message that redirects to another namespace

      • 2: Zlib

        • 1: Message: sender_id, text

        • 1: Message: sender_id, text

        • 1: Response: id

          • 1: Online: last_timepoint_seen

Number identifies the namespace used to decode the following message ID. Root namespace 0 redirects subsequent message creation to another namespace. A Compressed message selects a compression-algorithm namespace, and a Zlib message returns the stream decoder to namespace 1, where Message objects are decoded.

Message ID namespaces

MessageID uses a variable-length integer to encode the message index, allowing more than 256 messages in one namespace. Additional namespaces extend this capacity further. RequestID provides another way to reduce the number of IDs required.

Repeat message

Repeat messages are not encrypted. A Repeat message contains only a four-byte hash code, minimizing traffic and computation.

When one side needs to send exactly the same message again, it can send a Repeat message instructing the receiver to reuse the previous message.

The repeat code is calculated as Hash(Kdf(Meta::id, tx_key | Hash(Message))), with Meta::id incremented for every message. Hash(Message) prevents values from repeating when Meta::id wraps.

The receiver performs the same calculation. If the code matches, it repeats the previous message and replies with another Repeat message when the response is unchanged, or with a regular message when it has changed.

If the code does not match, the receiver discards the stored message and sends no reply because an attacker could inject arbitrary four-byte values.

A single invalid code disables repetition for that stored message. After receiving no reply, the sender retransmits the full encrypted and authenticated message.

Versioning

Protocol versioning is implemented by adding messages with new IDs. Existing message data formats are never changed.