Your files have locations. Your machines often do not.

A laptop in a hostel, a home server, and a small cloud instance may all be online while remaining separated by NAT, changing addresses, and different firewall rules. Saving a file on one of them is easy. Finding it later, reaching the right machine, and proving that another machine may retrieve it is the actual distributed-systems problem.

NostrMesh is my prototype for breaking that problem into responsibilities that can be reasoned about independently. A private WireGuard mesh handles reachability. Nostr carries signed metadata. A Blossom-compatible server holds encrypted, content-addressed blobs. Signed events authorize operations.

None of those ideas is novel by itself. The engineering question is whether their boundaries make failure easier to understand.

Distributed storage is not one database spread across machines. It is a set of agreements about reachability, naming, content, and authority.

First, Make the Machines Neighbors

NAT is useful. It lets many devices share a public address and prevents unsolicited inbound connections by default. It is also the reason a service running perfectly on a laptop can be unreachable from another network.

NostrMesh uses nostr-vpn to place participating nodes on a WireGuard mesh with stable private addresses. To the application, a storage service on another machine now looks like a neighbor on a private network instead of a changing public endpoint.

This does not mean WireGuard magically solves every NAT configuration. The mesh still needs identity, peer coordination, and a viable path between endpoints. What it does is give the storage layer a much cleaner contract: communicate over stable private routes rather than teaching every API how to traverse the public internet.

Metadata Is Not the File

Suppose a photo is five megabytes. A client looking for that photo should not need to retrieve five megabytes merely to learn that the object exists.

NostrMesh separates the description from the payload. A replaceable Nostr event—kind 34578, keyed by a SHA-256 identifier—acts as the metadata and discovery plane. It can describe the object and how it may be resolved. The encrypted bytes live in a Blossom-compatible blob service, which is designed around content-addressed objects.

The separation keeps each path small and purposeful. Relays carry compact signed events. Blob nodes handle larger byte streams. A client can discover first and transfer later.

PLANECARRIESOPTIMIZES FOR
MetadataSigned descriptionsDiscovery
BlobEncrypted bytesContent transfer
AuthorizationSigned intentAccess decisions

Encryption Belongs at the Blob Boundary

Content addressing and confidentiality solve different problems. A hash helps identify bytes and detect accidental or malicious changes. It does not stop a storage node from reading plaintext.

The NostrMesh blob plane encrypts payloads with AES-256-GCM. GCM provides both confidentiality and authentication: the content is unreadable without the key, and tampering causes verification to fail instead of producing silently corrupted plaintext.

That statement needs an important boundary. Encryption is only as strong as the surrounding key management and threat model. A private mesh reduces exposure; it does not make compromised hosts harmless. The prototype demonstrates the content path, not a complete zero-knowledge storage product.

TWO DIFFERENT GUARANTEES

The SHA-256 address answers “are these the bytes I expected?” AES-GCM answers “were these bytes kept confidential and authenticated under this key?” One does not replace the other.

Authorization as Verifiable Intent

Traditional APIs often pass a secret token that says, in effect, “the bearer is allowed.” Signed events express a different kind of evidence: a specific identity approved a specific action for a short period.

NostrMesh uses short-lived signed events of kind 24242 for blob authorization. The service validates the signature and request details before accepting an operation. That makes the authorization decision inspectable without requiring every node to share the same password database.

Signatures still do not decide policy on their own. The server must determine which public keys may perform which actions, reject expired requests, and prevent a valid event from being replayed outside its intended context. Cryptography proves who signed; application policy decides what that signature permits.

Retries Without Duplicate Damage

Networks fail in the least informative way possible. A client may send an upload, lose the response, and have no idea whether the server committed the operation.

The wrong response is to assume failure and create another object every time. NostrMesh’s API uses idempotency keys so repeated attempts can converge on the same logical result. If the same key is reused with conflicting input, the server can reject the ambiguity instead of manufacturing duplicate state.

Retry and backoff behavior complements that rule. A temporary failure can be tried again without turning resilience into corruption. PostgreSQL stores durable application state, Redis supports coordination, and the Docker Compose environment makes the multi-service topology reproducible for smoke and integration tests.

What the Prototype Does Not Prove

A diagram can make a prototype look more finished than it is, so the limits are worth stating plainly.

  • There is no documented production-scale throughput or availability result yet.
  • A private mesh narrows network exposure but does not remove key-management or compromised-node risk.
  • Encrypted blobs can still leave metadata patterns worth analyzing separately.
  • Reachability across arbitrary networks depends on the mesh’s real connectivity conditions.

Those are not footnotes to hide. They are the next design questions.

Separation Is the Architecture

NostrMesh is less about inventing a new storage primitive than arranging familiar primitives around clear responsibilities. WireGuard handles private reachability. Nostr carries signed metadata. Blossom stores encrypted blobs. Signed events carry authorization.

When a request fails, that separation gives the failure somewhere specific to live—and gives the next engineer a better chance of fixing the right layer.