shh.io: Architecture of a Zero-Knowledge Encrypted Notes Platform

Architectural decisions, applied cryptography, and modular packaging system behind shh.io, an open-source ephemeral notes platform with end-to-end encryption

@geomenaMon Jan 05 2026#cryptography#open-source#side-project862 views

The secure exchange of sensitive information stands as one of the most underestimated challenges in contemporary software engineering. Database credentials shared over Slack, API tokens sent via email, production secrets circulating through channels devoid of any encryption whatsoever: these regrettably commonplace patterns represent attack vectors that no security audit should tolerate.

shh.io emerges as a direct response to this problem. It is an ephemeral notes platform with end-to-end encryption, where every cryptographic operation executes exclusively on the client. The server never accesses plaintext, never receives encryption keys, and never stores information that would allow reconstruction of the original content. This property, known as zero knowledge, is not a design aspiration but a mathematically verifiable guarantee embedded in every line of the source code.

This document presents the architectural decisions, cryptographic algorithms, and modular packaging system that underpin the platform, with the technical rigor that each of these choices deserves.

Monorepo Architecture

The project adopts a monorepo structure managed with pnpm workspaces, which enables sharing code across modules without duplication, maintaining a single deterministic lockfile, and orchestrating interdependent builds with precision. The selection of pnpm over alternatives such as npm or Yarn reflects its symlink-based storage model, which drastically reduces disk consumption and enforces a strict dependency tree, eliminating accidental access to undeclared packages.

pnpm-workspace.yaml
package.json

Each package fulfills a well-defined responsibility: @geomena/crypto encapsulates the encryption algorithms with dual implementations for browser and Node.js, @geomena/lib orchestrates the business logic atop those cryptographic primitives, @geomena/cli exposes a comprehensive command-line interface, and @geomena/deploy-cloudflare coordinates deployment across Cloudflare's edge network.

The Cryptographic Engine

The cryptography layer constitutes the most significant core of value within the platform. Every decision in this module is grounded in verifiable standards, not arbitrary preferences.

AES-256-GCM: Authenticated Encryption

The selected algorithm is AES-256-GCM, a NIST-standardized authenticated encryption scheme that simultaneously provides confidentiality and integrity. Unlike modes such as CBC, which require an additional MAC to detect tampering, GCM incorporates a 128-bit authentication tag that invalidates any attempt to alter the ciphertext.

The implementation maintains two isomorphic variants, one for each execution environment:

crypto.web.aes-256-gcm.ts
const iv = createRandomBuffer({ length: 12 }); // 96-bit nonce
const key = await crypto.subtle.importKey(
  "raw", encryptionKey, "AES-GCM", false, ["encrypt"]
);
const encrypted = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv }, key, buffer
);
// Output format: base64url(iv):base64url(ciphertext)

The browser's SubtleCrypto API internally manages the GCM authentication tag, which simplifies the interface without compromising the security of the scheme.

crypto.node.aes-256-gcm.ts
const iv = createRandomBuffer({ length: 12 }); // 96-bit nonce
const cipher = createCipheriv("aes-256-gcm", encryptionKey, iv);
const encryptedBuffer = new Uint8Array([
  ...cipher.update(buffer),
  ...cipher.final(),
  ...cipher.getAuthTag(), // 16 authentication bytes, concatenated explicitly
]);

In Node.js, the authentication tag must be extracted and concatenated explicitly, since the crypto module does not automatically integrate it into the output buffer. During decryption, the last 16 bytes are separated and assigned as the auth tag before processing the ciphertext.

Both implementations generate a random 12-byte nonce per operation, which is the optimal size for GCM as specified by NIST SP 800-38D. The serialized output format, base64url(iv):base64url(ciphertext), ensures cross-platform portability and URL compatibility without requiring additional encoding.

Key Derivation: PBKDF2-SHA-256

Master key generation follows a rigorous derivation process. For each note, the system generates a base key of 32 cryptographically random bytes. When the user sets a password, it is concatenated with the base key and the result is subjected to PBKDF2 with the following parameters:

ParameterValueRationale
AlgorithmPBKDF2-SHA-256Standardized by NIST SP 800-132
Iterations100,000Exceeds the NIST minimum recommendation, resistant to brute force
SaltThe base key itself, 32 bytesUnique per note, prevents rainbow table attacks
Output length32 bytesFull AES-256 key
Master key derivation
const mergedBuffers = new Uint8Array([...baseKey, ...passwordBuffer]);
const masterKey = await deriveWithPbkdf2(
  mergedBuffers, // Input: base key + user password
  baseKey,       // Salt: the base key itself
  100_000,       // Iterations
  32,            // Resulting key length
  "sha256"
);

This design ensures that, even if two users encrypt notes with the same password, the derived keys will be entirely distinct, since each note possesses a unique base key. If no password is set, the base key functions directly as the encryption key, bypassing derivation without sacrificing the randomness of the cryptographic material.

Shamir's Secret Sharing over GF(256)

The most distinctive capability of the cryptographic module is the implementation of Shamir's Secret Sharing, a secret-sharing scheme with information-theoretic security. This algorithm enables splitting an encryption key into N shares, of which any subset of K shares reconstitutes the original secret, while K-1 shares reveal absolutely no information whatsoever.

The implementation operates over the finite field GF(256) with the irreducible polynomial 0x11D, employing precomputed lookup tables for multiplication and inversion operations:

GF(256) arithmetic — shamir.ts
const EXP_TABLE = new Uint8Array(256);
const LOG_TABLE = new Uint8Array(256);

// Table construction with irreducible polynomial 0x11D
let x = 1;
for (let i = 0; i < 255; i++) {
  EXP_TABLE[i] = x;
  LOG_TABLE[x] = i;
  x = (x << 1) ^ (x & 0x80 ? 0x11d : 0);
}

To split a secret, the algorithm constructs a polynomial of degree K-1 for each byte, where the constant term is the original byte of the secret and the remaining coefficients are random. Each share is obtained by evaluating that polynomial at a distinct point using Horner's method. Reconstruction employs Lagrange interpolation over GF(256), computing the Lagrange basis coefficients and accumulating the interpolated value at x=0.

Shamir's Secret Sharing — Key splitting, share distribution, and threshold recovery via Lagrange interpolation over GF(256)

The practical use case is revealing: an organization can encrypt a secret and distribute five shares among distinct custodians, requiring that at least three of them converge to decrypt the note. No individual custodian — not even two acting in collusion — can access the content.

Serialization with CBOR

The content of each note, along with its file attachments, is serialized using CBOR, a compact binary format standardized in RFC 8949. The choice of CBOR over JSON stems from precise considerations: smaller encrypted payload size, native support for typed arrays — which proves essential for the binary content of file attachments — and future extensibility without breaking format compatibility.

The serialized structure follows an array schema:

cbor-array.serialization.ts
// Structure: [content, [[metadata1, data1], [metadata2, data2], ...]]
const noteBuffer = encode([
  note.content,
  note.assets.map(({ content, metadata }) => [metadata, content]),
]);

This design decision allows a note to transport both text and arbitrary binary files within a single encrypted payload, preserving the integrity of each attachment's metadata, including name, MIME type, and size.

The Command-Line Interface

The @geomena/cli package transforms the full cryptographic power of the platform into an accessible and ergonomic terminal tool, built on the citty framework with interactive prompt support via @inquirer/prompts, spinners via ora, and colorization via picocolors.

Primary Commands

URL and Hash Fragment Strategy

A fundamental architectural decision lies in encoding encryption keys within the URL's hash fragment. Per the HTTP protocol specification, the hash fragment is never transmitted to the server, remaining exclusively within the browser or the client processing the URL. This behavior, far from being an implementation detail, constitutes the backbone of the zero-knowledge guarantee.

URL and Hash Fragment Strategy — The hash fragment never leaves the browser, preserving the zero-knowledge guarantee

The hash fragment format supports two variants. The standard variant follows the pattern #[pw][:dar]:base64url(encryptionKey), where the optional prefixes pw and dar indicate password protection and delete-after-reading, respectively. The variant for secrets shared via Shamir adopts the format #sss:threshold:totalShares:shareIndex:shareData[:pw][:dar], encoding all information necessary for share recombination into a single compact string.

Cloudflare Deployment: Uncompromising Edge Computing

The @geomena/deploy-cloudflare module represents a deliberate choice over conventional deployment platforms. While Vercel and AWS Lambda execute serverless functions in specific regions with cold-start latencies ranging from 50 to 200 milliseconds, Cloudflare Workers operates across more than 300 edge locations with startup times below 10 milliseconds — a difference that proves significant for an application whose server interaction is limited to read and write operations on encrypted payloads.

The build process orchestrates the compilation of the server — an ESM bundle generated by esbuild targeting Cloudflare's workerd runtime — and the client — a SPA built with SolidJS and Vite — unifying both artifacts into a single distribution directory:

build.sh — Deployment orchestration
# Server compilation as Cloudflare Worker
pnpm --filter @geomena/app-server build:cloudflare
# Result: _worker.js (minified ESM bundle)

# Client compilation as static SPA
pnpm --filter @geomena/app-client build
# Result: Vite-optimized static assets

# Artifact unification
cp -r ../app-server/dist-cloudflare/* dist/
cp -r ../app-client/dist/* dist/
cp _routes.json dist/

The route configuration, defined in _routes.json, establishes that only requests to /api/* are directed to the Worker, while all static assets are served directly from Cloudflare's CDN without additional processing. Encrypted note storage employs Cloudflare KV, a globally distributed key-value store with native TTL support, which aligns perfectly with the ephemeral nature of the platform's notes.

Modular Packaging System

The build strategy constitutes an engineering aspect that warrants particular attention, as each package in the monorepo employs the optimal tool for its execution context.

The @geomena/crypto package uses unbuild, a Rollup-based tool that simultaneously generates ESM and CommonJS modules with type declarations. The conditional exports configuration in package.json allows the same package to automatically resolve the correct implementation based on the environment:

package.json — Conditional exports for @geomena/crypto
{
  "exports": {
    "browser": "./dist/index.web.mjs",
    "worker": "./dist/index.web.mjs",
    "node": {
      "import": "./dist/index.node.mjs",
      "require": "./dist/index.node.cjs"
    }
  }
}

This configuration guarantees that a browser or Cloudflare Worker receives the SubtleCrypto-based implementation, while a Node.js environment automatically resolves to the native crypto module, without the consuming code needing to be aware of this distinction. The Node.js module resolution system and modern bundlers interpret these conditions at build time or import time, eliminating any runtime overhead.

The server, in turn, is bundled with esbuild into a single minified bundle, prioritizing compilation speed and the reduced size demanded by the Cloudflare Workers environment, whose script size limit is 10 MB compressed.

PackageBuild ToolOutput FormatRationale
@geomena/cryptounbuildESM + CJS + typesConditional resolution by platform
@geomena/libunbuildESM + CJS + typesUniversal consumption from CLI and client
@geomena/cliunbuildESM + CJSNode.js executable with bin entry
@geomena/app-serveresbuildESM bundleMinimal bundle for Cloudflare Workers
@geomena/app-clientViteStatic SPAAsset optimization for CDN

Verifiable Security Properties

The platform's security guarantees do not depend on trust in the server or the deployment infrastructure, but rather on mathematically verifiable properties within the source code:

  • Confidentiality: AES-256-GCM with 256-bit keys and random 96-bit nonces per operation.
  • Integrity: The 128-bit GCM authentication tag invalidates any tampered ciphertext.
  • Brute-force resistance: PBKDF2 with 100,000 iterations over SHA-256.
  • Zero knowledge: Keys travel exclusively within the hash fragment, which the HTTP protocol excludes from transmission to the server.
  • Per-note forward secrecy: Each note generates a unique 32-byte base key; the compromise of one note does not affect any other.
  • Information-theoretic security: Shamir's Secret Sharing guarantees that K-1 shares reveal absolutely nothing about the secret — a property that depends not on computational limitations but on the algebraic structure of the scheme.
  • Cryptographic randomness: Both crypto.getRandomValues() in browsers and crypto.randomBytes() in Node.js draw entropy from the operating system.

Open Source and Community

shh.io is an open-source project published under the Apache 2.0 license, a deliberate decision reflecting the conviction that security tools must be auditable, reproducible, and accessible without restrictions. In the domain of applied cryptography, opacity is not synonymous with security but with fragility: a system whose robustness depends on the secrecy of its implementation does not deserve the trust it purports to inspire.

The value this project contributes to the community transcends the functionality of sharing encrypted notes. The cryptography modules, with their dual implementations for browser and Node.js, the finite field arithmetic for Shamir's Secret Sharing, and the platform-conditional exports system, constitute reference material for any engineer confronting similar challenges in their own projects. The CLI system demonstrates how to build terminal tools that do not sacrifice ergonomics in favor of underlying technical complexity. The Cloudflare Workers deployment strategy offers a replicable model for applications that prioritize global latency without the operational complexity of traditional infrastructure.

The repository is available at github.com/geo-mena/shh.io, where every architectural decision documented in this article can be verified directly in the source code. Contributions — whether in the form of security reviews, new encryption algorithms, command-line interface improvements, or adaptations to other infrastructure providers — are welcome and represent exactly the kind of collaboration that strengthens the ecosystem of security tools available to the community.