Skip to content

Architecture

Swarm ID uses an OAuth-style popup authentication flow. Chrome and Firefox work out of the box; where the browser partitions the proxy iframe’s storage — Safari’s Intelligent Tracking Prevention, or strict privacy settings elsewhere — the connect popup hands the iframe the account’s upload credentials directly, so uploads keep working. See #277 for the background.

The architecture consists of three main components:

┌─────────────────────────────────────────────────────────────┐
│ Your dApp (e.g., https://my-dapp.com) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ SwarmIdClient │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────┐ │ │
│ │ │ Hidden iframe (https://swarm-id.snaha.net) │ │ │
│ │ │ │ │ │
│ │ │ SwarmIdProxy │ │ │
│ │ │ - Stores app-specific secret │ │ │
│ │ │ - Proxies Bee API calls │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ │ Opens popup │
│ ↓ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Auth Popup (https://swarm-id.snaha.net) │ │
│ │ │ │
│ │ Auth Popup (SvelteKit /connect route) │ │
│ │ - Stores master key (first-party context) │ │
│ │ - Derives app-specific secrets │ │
│ │ - Sends secret to iframe via postMessage │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

The main library used by parent dApp windows. It:

  • Creates and embeds a hidden iframe from the trusted domain
  • Provides authentication UI (login button, status)
  • Exposes methods for Bee operations (upload, download, etc.)
  • Communicates with the iframe via postMessage

Runs inside the hidden iframe on the trusted domain. It:

  • Receives app-specific secrets from the auth popup
  • Stores secrets in partitioned localStorage
  • Proxies Bee API calls with authentication
  • Validates all incoming messages from the parent

The authentication popup is implemented as a SvelteKit route (/connect) in the identity management UI. It:

  • Manages the master key in first-party localStorage
  • Derives app-specific secrets using HMAC-SHA256
  • Sends secrets to the iframe via postMessage
  • Provides the authentication UI (Passkey/WebAuthn and SIWE flows)

All cryptographic keys are derived from a single master key produced during Passkey or SIWE authentication:

Master Key (from Passkey/SIWE challenge)
├─> App-Specific Secret (HMAC-SHA256 with app origin)
│ ├─> Low-stakes keys (feed, session) → shared with apps
│ └─> High-stakes keys (stamps, ACT) → never shared
├─> derivationKey (HMAC-SHA256 with "derivation-key", persisted in account)
│ ├─> swarmEncryptionKey (HMAC-SHA256 with "swarm-encryption")
│ │ ├─> backup AES key (HMAC-SHA256 with "swarm-id-backup-encryption-v1")
│ │ │ └─> AES-GCM-256 CryptoKey (for .swarmid encryption)
│ │ └─> backup feed key (HMAC-SHA256 with "backup-key")
│ │ └─> backup private key (for Swarm epoch feed owner)
│ └─> postageSignerKey (HMAC-SHA256 with "postage-signer" or "postage-signer:{identityId}")
│ └─> PrivateKey for signing postage batch chunks
// Pseudocode for key derivation
appSecret = HMAC - SHA256(masterKey, appOrigin)

This ensures:

  • Deterministic - Same master key + app origin always produces the same secret
  • Unique per app - Each app gets a different secret
  • No cross-app leakage - Knowing one app’s secret doesn’t reveal others
  • Master key protection - The master key never leaves the trusted domain

Modern browsers partition storage by (iframe-origin, parent-origin). This means:

  • An iframe from swarm-id.snaha.net embedded in app-a.com has different storage than the same iframe embedded in app-b.com
  • This provides browser-enforced isolation between apps
  • Even if an app is compromised, it can’t access other apps’ secrets

All cross-origin communication uses postMessage with Zod validation:

Message Purpose
parentIdentify Identify parent to iframe
checkAuth Check authentication status
requestAuth Request authentication (open popup)
uploadData Upload data to Swarm
downloadData Download data from Swarm
Message Purpose
proxyReady Iframe is initialized
authStatusResponse Authentication status
authSuccess Authentication completed
uploadDataResponse Upload result
downloadDataResponse Download result
error Error occurred
Message Purpose
setSecret Send derived app-specific secret

Swarm ID supports account backup via encrypted .swarmid files and automatic Swarm-based sync.

Each .swarmid file is a JSON object with two parts:

  1. Plaintext header — account metadata visible without decryption (account name, export timestamp, account type, and type-specific fields)
  2. Encrypted payload — AES-GCM-256 encrypted AccountStateSnapshot containing identities, connected apps, and postage stamps

Header fields vary by account type:

Account Type Extra Header Fields
Passkey credentialId
Ethereum ethereumAddress
Agent (none)

During Ethereum account import, the user must re-enter their secret seed. The master key is re-derived from secretSeed + publicKey — key material is never stored in the backup header.

Export requires no re-authentication — the derivationKey is already persisted in the account. The flow:

  1. Serialize account state via serializeAccountStateSnapshot() (strips appSecret)
  2. Derive swarmEncryptionKey from derivationKey via HMAC-SHA256 with context "swarm-encryption"
  3. Derive AES-GCM-256 key: swarmEncryptionKey → HMAC-SHA256 with context "swarm-id-backup-encryption-v1"
  4. Encrypt with random 12-byte IV
  5. Build header with account metadata + ciphertext

Import requires authentication to re-derive the encryption key:

  1. Parse the plaintext header to identify account type and metadata
  2. User authenticates (passkey or wallet) to obtain masterKey
  3. Derive derivationKeyswarmEncryptionKeybackupKey → AES-GCM-256 key
  4. Decrypt and validate the payload via deserializeAccountStateSnapshot()

When a passkey auth succeeds on a new device with no local account:

  1. Derive derivationKey from masterKey
  2. Derive swarmEncryptionKey from derivationKey
  3. Derive backupKey → epoch feed owner private key
  4. Build feed topic from account ID
  5. Look up latest epoch feed entry in Swarm
  6. Download and decrypt the account snapshot

The appSecret field is structurally excluded from ExportedConnectedAppSchemaV1. Even if raw data contains an appSecret, Zod strips it during parsing. App secrets are re-derived on-demand from the master key — they are never stored in backups.

  • Master key - Only stored in first-party context on trusted domain
  • App secrets - Derived per-app, stored in partitioned storage
  • Cross-app isolation - Browser-enforced through storage partitioning
  • XSS in dApp - Can’t access master key (different origin)
  • Malicious dApp - Only gets its own derived secret
  • Man-in-the-middle - HTTPS required for all communication
  • Message spoofing - All messages validated with Zod schemas
  • Always use HTTPS in production
  • Validate the iframeOrigin matches your trusted domain
  • Keep the trusted domain secure and audited
  • Use Content Security Policy to restrict iframe embedding