Encrypted store-carry-forward SDK for .NET 10.
Durable queues, recipient-key payload encryption, and a three-node in-process simulator.
Run the 60-second demo · Explore the architecture · Pick an issue · Join the discussion
RelayOS explores how a packet can be encrypted for its recipient, stored on a device, carried across a disconnected interval, and forwarded during a later encounter. Relays can inspect the routing metadata required by the MVP, but not the protected payload.
Current scope: Core SDK + in-process network simulator. There is no Bluetooth, Wi-Fi Direct, mesh radio, background discovery, production routing, or delivery-receipt protocol yet.
| Works today | Not implemented yet |
|---|---|
| Encrypted packets, durable file queues, TTL, priority, deduplication, and simulated Alice → Courier → Bob delivery | Physical device discovery or transport, authenticated sender identity, production routing, receipts, and an audited protocol |
Most communication software assumes that the internet exists, stays reachable, and stays affordable. RelayOS explores the opposite condition: devices that meet briefly, carry data forward, and sync opportunistically.
The long-term direction is an open, inspectable protocol for offline-first communication experiments, disaster-resilient coordination, field research, campus/local communities, and privacy-conscious apps that need more than a cloud queue. The current code is intentionally small, testable, and honest about what is not done yet.
With the .NET 10 SDK installed, run the complete three-node simulation:
dotnet run --project samples/RelayOS.Simulator -- "Hello through a disconnected relay"The command creates Alice, Courier, and Bob in one process. Alice and Bob never connect directly: the courier persists the ciphertext, closes and reopens its queue, and later forwards the packet to Bob.
ALICE encrypted and queued a packet for Bob
HOP 1 Alice → Courier: ciphertext persisted
CHECK Courier cannot decrypt the payload ✓
HOP 2 Courier → Bob: packet forwarded
BOB decrypted: “Hello through a disconnected relay”
This is a deterministic local simulation; it does not access device radios or the internet.
RelayOS.Core, targetingnet10.0- recipient-key payload encryption using .NET cryptography APIs
- a file-backed JSON queue with atomic replacement on commit
- TTL enforcement, four priority levels, and packet-ID deduplication
- a transport abstraction for future adapters
LocalRelayHub, which simulates explicit peer encounters in one process- a three-node store-carry-forward scenario: Alice → Courier → Bob
- a one-command console simulator using only the standard .NET SDK
- Core tests that do not require a MAUI workload
- a .NET MAUI Android source demo for visualizing the local simulation
RelayOS is a technical demonstration, not a production communications system. It has not received an independent security review. Do not depend on this MVP for emergencies, medical care, public safety, military use, or any situation in which delayed, duplicated, disclosed, or lost data could cause harm.
Application / MAUI demo
│
▼
RelayNode ─────────────── RelayIdentity / RelayCryptography
│ │
├── IRelayPacketStore └── encrypted RelayPacket
│ └── FileRelayPacketStore
│
└── IRelayTransport
└── LocalRelayTransport ─── LocalRelayHub
(same process only)
The main components are:
| Component | Responsibility |
|---|---|
RelayNode |
Creates, queues, forwards, accepts, and decrypts packets. |
RelayPacket |
Carries public routing metadata and encrypted payload fields. |
RelayIdentity |
Owns a node's P-256 ECDH private key and exposes its public key. |
RelayCryptography |
Encrypts for a recipient and decrypts at that recipient. |
IRelayPacketStore |
Defines queue, delivery-state, expiry, and statistics operations. |
FileRelayPacketStore |
Persists a node's queue as one JSON document. |
IRelayTransport |
Defines broadcast and receive operations without choosing a radio. |
LocalRelayHub |
Simulates links between nodes created in the same process. |
RelayNode.SendAsyncencrypts a payload for a suppliedRelayPublicKeyand persists the resulting packet.ForwardPendingAsyncasks the transport to broadcast pending, unexpired packets to peers that are currently connected.ReceiveAvailableAsyncvalidates each packet independently. A relay stores the encrypted packet; the destination authenticates it before durable acceptance.- After a simulated contact changes, the carrier broadcasts its queued copy.
- The recipient decrypts the packet and marks its local record delivered. A pending destination packet left by an interrupted earlier run is recovered from the queue on the next receive pass.
Forwarding does not remove the sender's or carrier's copy. Repeated broadcasts are expected; each receiving store uses the packet ID and an immutable packet digest to reject exact duplicates. Reusing an ID for different packet content is treated as a conflict. Records, including delivered records, are removed when their TTL expires, after which that store no longer remembers the packet ID.
| Setting | MVP value |
|---|---|
| Default TTL | 12 hours |
| Maximum TTL | 7 days |
| Maximum plaintext payload | 256 KiB |
| Priorities | Low, Normal, High, Critical |
| Queue order | Priority descending, then creation time, then packet ID |
| Default content type | application/octet-stream |
TTL is evaluated against each device's local UTC clock. It is an expiry policy, not a guarantee that every copy has been deleted at the same real-world instant.
For every packet, RelayOS currently uses:
- an ephemeral NIST P-256 ECDH key pair at the sender;
- the recipient's static P-256 ECDH public key;
- HKDF-SHA-256 with a fresh 32-byte salt and a versioned RelayOS context;
- AES-256-GCM with a fresh 12-byte nonce and a 16-byte authentication tag.
The AES-GCM additional authenticated data covers the protocol version, packet ID, sender and recipient IDs, timestamps, priority, and content type. A relay can read this routing metadata but cannot decrypt or silently modify the protected payload. Derived secret material is cleared from managed buffers after use where the .NET APIs permit it.
Important security boundaries:
- The current packet format does not include a sender signature. The claimed
SenderIdis integrity-protected after encryption but is not proof of who created the packet. - Public-key discovery, verification, rotation, revocation, and recovery are outside the MVP. The caller must obtain the correct recipient public key through a trusted channel.
- The Core library can export a PKCS#8 private key, but it does not provide secure key storage. An application must use an appropriate platform keystore.
- Packet payloads are encrypted in the file queue; IDs, endpoints, timestamps, priority, content type, and packet sizes remain visible.
- The SHA-256 queue digest detects inconsistent content for a packet ID and accidental corruption. It is not a keyed file-integrity mechanism against an attacker who can rewrite the queue file.
- This custom protocol has not been audited and does not provide traffic-analysis resistance or forward secrecy after compromise of the recipient's static private key.
Install the .NET 10 SDK. The repository's global.json selects SDK 10.0.201 and permits newer patches in that feature band.
Build the Core library and run its tests without installing MAUI:
dotnet restore tests/RelayOS.Core.Tests/RelayOS.Core.Tests.csproj
dotnet build src/RelayOS.Core/RelayOS.Core.csproj --configuration Release --no-restore
dotnet test tests/RelayOS.Core.Tests/RelayOS.Core.Tests.csproj --configuration Release --no-restoreRun these commands from the repository root.
This example creates three nodes and explicitly changes which simulated peers can meet. Alice and Bob are never connected directly.
using System.Text;
using RelayOS.Core;
using RelayOS.Core.Cryptography;
using RelayOS.Core.Models;
using RelayOS.Core.Storage;
using RelayOS.Core.Transport;
var dataDirectory = Path.Combine(Path.GetTempPath(), $"relayos-{Guid.NewGuid():N}");
var hub = new LocalRelayHub();
using var aliceIdentity = RelayIdentity.Create("alice");
using var courierIdentity = RelayIdentity.Create("courier");
using var bobIdentity = RelayIdentity.Create("bob");
using var aliceStore = new FileRelayPacketStore(Path.Combine(dataDirectory, "alice.json"));
using var courierStore = new FileRelayPacketStore(Path.Combine(dataDirectory, "courier.json"));
using var bobStore = new FileRelayPacketStore(Path.Combine(dataDirectory, "bob.json"));
var alice = new RelayNode(aliceIdentity, aliceStore, hub.CreateTransport("alice"));
var courier = new RelayNode(courierIdentity, courierStore, hub.CreateTransport("courier"));
var bob = new RelayNode(bobIdentity, bobStore, hub.CreateTransport("bob"));
await alice.SendAsync(
bobIdentity.PublicKey,
Encoding.UTF8.GetBytes("Hello through a carrier"),
new RelaySendOptions
{
TimeToLive = TimeSpan.FromHours(1),
Priority = RelayPriority.High,
ContentType = "text/plain; charset=utf-8"
});
hub.Connect("alice", "courier");
await alice.ForwardPendingAsync();
await courier.ReceiveAvailableAsync(); // encrypted packet enters the carrier queue
hub.Disconnect("alice", "courier");
hub.Connect("courier", "bob");
await courier.ForwardPendingAsync();
var report = await bob.ReceiveAvailableAsync();
var text = Encoding.UTF8.GetString(report.Delivered.Single().Payload);
Console.WriteLine(text);The carrier has no recipient private key, so it cannot decrypt Bob's payload. Connect and Disconnect only change links inside LocalRelayHub; they do not interact with physical radios or the network.
The MAUI project presents the same Alice → Courier → Bob simulation on one device. All three nodes and every simulated link live inside that single app process. It is a visualization of the Core workflow, not a connection among multiple phones. The Core CI does not build this project, so treat it as source demo rather than a verified Android release.
Install or restore the Android workload, then build the Android target:
dotnet workload restore src/RelayOS.Demo/RelayOS.Demo.csproj
dotnet build src/RelayOS.Demo/RelayOS.Demo.csproj --framework net10.0-androidLaunch it on an Android emulator or connected device from an IDE with .NET MAUI support, or use the platform tooling installed with your MAUI workload. The Core-only CI workflow intentionally does not restore or build the MAUI project.
src/RelayOS.Core/ Packet model, cryptography, queue, node, transports
src/RelayOS.Demo/ .NET MAUI Android demonstration
samples/RelayOS.Simulator/ One-command standard-SDK console simulation
tests/RelayOS.Core.Tests/ Core unit and store-carry-forward tests
.github/workflows/core.yml Core-only continuous integration
The following capabilities are not implemented:
- Bluetooth, Bluetooth LE, Wi-Fi Direct, Nearby Connections, or any other physical transport
- mesh discovery, peer negotiation, radio permissions, or background discovery
- Android background services, wake-up scheduling, or operation while the app is suspended
- production routing, path selection, congestion control, backpressure, quotas, or fairness
- hop limits or persistent per-peer forwarding history
- sender authentication, trusted key exchange, a PKI, key rotation, or revocation
- delivery acknowledgements or receipts back to the sender
- multi-process queue locking, database transactions, recovery journals, or encrypted metadata
- protection from malicious peers, replay after deduplication state expires, denial of service, or traffic analysis
- interoperability guarantees across protocol versions
IRelayTransport is an extension point, not evidence that a real radio transport exists. The local simulator broadcasts every pending packet to every explicitly connected in-process peer. It should not be described as Bluetooth mesh or offline networking between devices.
Potential next steps, roughly in dependency order:
- Specify and independently review a versioned wire protocol and threat model.
- Add authenticated device identities, signed packets, key verification, rotation, and revocation.
- Add queue quotas, streaming/chunking, crash recovery, per-peer state, hop limits, and replay-retention rules.
- Define contact negotiation and capability exchange for transport adapters.
- Prototype an Android foreground transport using one supported nearby-device API, with explicit permission and lifecycle handling.
- Add signed delivery receipts and an acknowledgement retention policy.
- Test interoperability, hostile inputs, long-running relays, clock skew, and large simulated topologies.
- Commission external protocol and implementation security reviews before any production claim.
Roadmap items are intentions, not current features.
See CONTRIBUTING.md for the development workflow and the additional review expected for protocol or cryptography changes.
Start with an issue labelled good first issue, ask design questions in Discussions, and review the Code of Conduct. Project direction and decision rules are documented in GOVERNANCE.md.
Good first areas for contributors:
- deterministic tests for hostile packet and queue inputs
- protocol notes, diagrams, and threat-model review
- Android lifecycle research for a future real-device transport
- sample apps that clearly label simulated behavior
- documentation fixes that make the MVP limits easier to understand
RelayOS was created and is led by Rufat Asadzade. Contributors receive visible credit for meaningful work. Forks are welcome under AGPL-3.0; use a distinct name and preserve attribution so users can identify the official project. See NOTICE and TRADEMARKS.md.
Please do not open public issues for sensitive vulnerabilities. See SECURITY.md for the reporting process.
RelayOS source code is available under the GNU Affero General Public License v3.0 only.
The RelayOS name, logo, marks, and project identity are not included in that license. See TRADEMARKS.md.