# Scalable encrypted chat archives

October 4, 2026. Local Web3 application integration and public **Monad testnet
10143** index. No Git push, new Oracle web deployment or mainnet launch in this
iteration. This is a tested architecture foundation, not a capacity certificate
for 120 million messages or a completed production cryptography audit.

## Data path

```mermaid
flowchart LR
  A[Authorized source: 32 conversations / 100 messages] --> B[Browser encryption]
  B --> C[Bounded raw CID blocks]
  C --> D[Configured storage replicas / IPFS pins]
  D --> E[Signed reference queue]
  E --> F[Leased workers with separate gas wallets]
  F --> G[Monad: digest, stream, sequence, epoch]
  G --> H[Independent client: chosen conversation and page]
  D --> H
  H --> I[Verify digest, recover key, decrypt locally]
```

## Implemented boundaries

- `OrbChatIndex` references the existing immutable `OrbChatArchive` recovery
  identity. Wallet keys and old full-onchain archives are preserved. The new
  contract has no funds, approvals, spending delegation, administrator, setter
  or upgrade path. Chat sessions remain revocable and bounded to 24 hours.
- Each encrypted content block is at most 256 KiB; an archive page contains up
  to 100 messages and 96 KiB of plaintext. Raw CIDv1/SHA-256 matches the digest
  anchored in the index. Names and text stay inside authenticated encryption.
- Onchain catalogs and reference pages return at most 32 entries. A sequence
  compare-and-append prevents another device overwriting a committed page.
  Clients read state directly, avoiding scans through years of event logs.
- The browser export uses an indexed 100-message source and at most eight source
  pages per operation. Per-conversation high-water marks advance only after
  durable encrypted queue acknowledgement. Continue operations resume history;
  six-hour preparation is incremental and only runs in an active unlocked
  browser panel. It is not an always-on server backup of every community.
- Encrypted staged blocks live in IndexedDB, scoped to index and wallet, with a
  64-entry bound. Cross-device pending queue retrieval is authenticated and
  paginated. Known publication hashes survive reload and must be checked before
  another send. A provider losing a response before returning a hash is still an
  external ambiguity: inspect wallet activity rather than blindly retrying.
- Recovery opens one catalog page and at most four encrypted content blocks per
  view. Switching pages replaces the decrypted page. Wallet switching clears
  keys and private UI state. A bad/unavailable gateway is skipped; a block is
  accepted only if its exact length and SHA-256 match the chain reference.
- Uploads require the Orb session and its verified bound Privy wallet, signature,
  current access, matching context and bounded request body. Public digest GETs
  expose encrypted bytes only, with CORS, immutable cache headers and no script
  execution. Operators cannot recover wallet keys from the relay queue.
- Default per-account upload budget is 8 MiB/day (UTC accounting). Atomic SQL
  reservations charge a particular digest once even after upload retries. Disk,
  HTTP/CDN and provider capacity remain real resource limits: `storeEncryptedBlock`
  refuses to write when any folder volume has less than `ORB_CHAT_MIN_FREE_BYTES`
  (2 GiB default) free. Reads are never blocked by that floor.
- SQL also enforces the 64-reference pending limit across concurrent devices.
  Partial shard/person indexes exclude confirmed history from worker/queue
  scans. Source catalog cursors are pushed into each authorized SQL branch;
  the final union sorts at most 66 candidate rows per page.

## Shared community content

The protocol supports one encrypted shared content block and separate small
wallet-specific **epoch key grants**, avoiding a copy of the text for every
recipient. Grant work is proportional to members per epoch, not messages times
members. Grants and catalogs are independently paged/batched.

Only the current independent registry owner can archive a canonical community
stream. Recipients must currently have registry access and registered recovery
keys when their grant is anchored. The owner opens a new epoch when the included
membership changes; new publications cannot use an old epoch after rotation.
Old recipients retain old keys and content, and authorized recipients can share
what they have already decrypted. There is no MLS/Double Ratchet forward secrecy.
Historical grant author identity remains bound to its epoch across owner changes.

The UI provides an explicit owner rehearsal for a shared text block in a selected
independent community. It does **not automatically migrate private hosted room
history or private channel permissions into that public registry**. Existing
hosted exports remain personal historical copies. Automatic shared hosted history
requires a separately reviewed room/membership bridge and epoch rotation policy;
it must not silently broaden a private channel's recipients. Personal copies do
not prove another user's authorship. New indexed envelopes authenticate their
publisher, not the truth of copied text.

## Storage and operations

`ORB_CHAT_STORAGE_ROOTS` is a JSON array of absolute server paths. Writes use
digest-sharded folders and atomic file replacement, then read back and verify
the bytes. `ORB_CHAT_KUBO_NODES` supports configured Kubo RPC nodes with optional
server-only authorization; raw blocks are pinned and read back before success.
Use independent hosts/storage providers for production. Two folders on the same
computer do not survive the loss of that computer.

`ORB_CHAT_STORAGE_QUORUM` defaults to two verified replicas. Failed quorum means
no successful durable acknowledgement. The keeper rechecks/repairs replicas from
verified ciphertext before anchoring references. Known corruption does not make
unverified data readable. A node endpoint is operator configuration, never a
user-submitted URL. RPC/pinning credentials never enter the client bundle.

Public `chat-index.deployment.json` lists read gateway templates, using `{cid}`
or `{digest}`. Independent clients may choose their own HTTPS gateways; local
HTTP is allowed only for localhost fixtures. Current configuration includes the
local preview endpoint and public IPFS gateways. Local files are **not** pinned
on public IPFS merely because they have a CID. Independent network persistence
requires configured live pins/replicas. [IPFS persistence documentation](https://docs.ipfs.tech/concepts/persistence/).

`chat-index-keeper.mjs` uses a dedicated testnet operator gas key, never a user
wallet key. Jobs use atomic SQL leases, bounded scans and retry backoff. Shards
divide streams by digest prefix (1–256 power-of-two shards). Each concurrently
running worker needs its own gas wallet and durable journal volume: sharing a
gas wallet across machines/services is unsupported. Do not share its wallet with
the old archive keeper. Raw gas transactions are journaled and fsynced before
broadcast; interruption resumes the same hash. Exact receipt events are required
before queue rows are marked confirmed. Default test MON budget is 0.5/day per
worker; fleet-wide spending must be managed by the operator.

Database records contain small signed references, leases and quotas; message
ciphertext is kept in the blob layer. For a larger fleet, partition metadata
databases by the same stream prefix and keep a routing directory. The current
application database is still libSQL/SQLite; no distributed database cluster or
automatic cross-database router is installed by this change. Run CDN/gateway
caching, independent replica availability checks, disk capacity alerts, retrieval
and recovery drills before calling a deployment production ready.

Example environment (values are placeholders, not usable credentials):

```dotenv
ORB_CHAT_STORAGE_ROOTS=["/srv/orb-chat/blocks"]
ORB_CHAT_KUBO_NODES=[{"id":"offsite-a","url":"https://your-private-kubo-rpc.example/","authorization":"Bearer SERVER_ONLY_SECRET"}]
ORB_CHAT_STORAGE_QUORUM=2
ORB_CHAT_MIN_FREE_BYTES=2147483648
ORB_CHAT_UPLOAD_BYTES_PER_DAY=8388608
ORB_CHAT_INDEX_RELAYER_KEY=DEDICATED_TESTNET_OPERATOR_KEY
ORB_CHAT_INDEX_SHARDS=1
ORB_CHAT_INDEX_SHARD=0
ORB_CHAT_DAILY_BUDGET_MON=0.5
```

Apply migrations `0047` through `0049` to the intended Web3 database. The local helper
`node scripts/setup-chat-scale-local.mjs` refuses remote/out-of-checkout databases
and configures two same-machine development folders. `npm run chat:scale:keeper`
runs the reference relay. No Oracle daemon was installed here.

## Capacity evidence and remaining gates

4,000 × 30,000 is 120 million messages. At 500 bytes each, raw text alone is
60 GB decimal. Encryption, metadata, attachments, replicas, key grants and any
remaining personal copies add costs. This architecture moves bulk bytes offchain;
references, key registration/grants and epochs still consume gas and state.
Neither batching nor Monad TPS promises free or unlimited storage.

At exactly 100 messages per content block, that scenario produces 1.2 million
references before metadata and key grants, or at least 37,500 fully occupied
32-reference publications. The index stores each reference, not only one global
Merkle root per six hours. Initial backfills need a measured gas/time budget;
longer messages and personal duplicates increase those figures. A future Merkle
checkpoint protocol would need independent manifest/proof availability and its
own recovery/security review rather than silently changing this contract.

Verified separately:

- 30,000 synthetic messages in one local EVM stream: 300 encrypted blocks, each
  below 80 KiB in that fixture, with two local replicas. One selected recovery
  page read 400 messages from four blocks. This was not a 120-million-message
  end-to-end run.
- 4,000 accessible communities in a disposable local SQL database: 125 catalog
  pages, never more than 32 conversations returned per query; other accounts
  could not see those communities or read their private rooms.
- Foreign wallet/signature, modified content/context, replay/sequence conflict,
  revoked session, failed replication quorum and corrupted gateway rejection.
  A revoked member's old epoch key cannot decrypt new-epoch content.
- Actual leased local keeper publication, exact receipt verification and repeat
  run without duplicate publication. Privy ownership is mocked in API fixtures.
- Concurrent content writers, idempotent upload accounting, the atomic 64-job
  bound, live/expired worker leases and room-to-DM cursor boundaries. A Windows
  concurrent replacement is accepted only after verifying the installed file's
  exact size and SHA-256; failed/missing/corrupt replicas still reject quorum.
- Relocated browser readers with Orb API unavailable, wallet recovery, paged
  private archive, bad replica fallback, mobile/EN/PT and wallet-switch clearing.
- Existing E2EE recovery/API/onboarding regressions, independent ZIP integrity,
  313-asset configured-secret scan, fresh production build/typechecking and
  unauthenticated/cross-origin production HTTP security fixtures. Production
  HTTP fixtures use a blank local database, not real user wallet transactions.

Reproduce the focused protocol/server checks with `npm run chat:scale:test`;
recovery browser checks use `npm run chat:test:ui`. Run
`node scripts/test-independent-kit.mjs` and
`node scripts/test-chat-public-assets.mjs` after rebuilding the public client.
The HTTP gate is `node scripts/check-production-http.mjs --only=production-security`
with a 2 GiB Node heap limit; it isolates the build and disposable database.

Public testnet index:
[`0xa44874D7Bb59DDadc264e3C6Cbe04dD11464625e`](https://testnet.monadscan.com/address/0xa44874D7Bb59DDadc264e3C6Cbe04dD11464625e).
Deployment:
[`0x375067…9dc7`](https://testnet.monadscan.com/tx/0x375067222f8c8d7199931e94c4f8dd5b60009cf84990ccc7012d382ace139dc7).

Real Privy acceptance on another authorized origin, public offsite pinning,
production load/failure testing, continuous monitoring and an independent
cryptographic review remain required. An archive key cannot reconstruct missing
files. Attachments/calls are not exported, hosted live membership stays hosted,
and losing a legacy key still makes its old ciphertext unreadable.
