Prerelease: v1.0.0-rc1

Post Protocol

A post in Hashiverse is HTML — authored in the browser with a rich-text editor, DOMPurify sanitized before display. Between authorship and display is a pipeline of compression, encryption, signing, two-phase submission, DHT routing, and bundle aggregation. This page follows a post through that pipeline.

The encoded post

EncodedPostV1 is the wire format for a single post. It contains:

Encryption uses multiple passphrases — one per context the post appears in — via a custom multi-key scheme inspired by the age encryption format. A post that is both in a user's timeline and under a hashtag is encrypted with both the user's public ID and the hashtag string as passphrases. Either key decrypts it. Servers holding the encrypted bytes cannot read either.

Submission: Claim then Commit

Post submission is a two-phase protocol:

  1. SubmitClaim: The client sends a claim request with PoW. The server validates the PoW and, if the post ID is not already present, issues a token granting permission to commit.
  2. SubmitCommit: The client sends the actual post bytes with the token. The server stores the post and returns a confirmation signature.

Separating claim from commit prevents payload-flooding attacks: a server can reject bad-faith claims cheaply (just verify PoW) before accepting any large payload. The confirmation signature from the server is the client's proof that the post was accepted.

Source: hashiverse-server/src/server/handlers/

Bundles and buckets

Posts are not stored or fetched individually — they are grouped into EncodedPostBundleV1 objects, one bundle per bucket per server. A bundle contains around 20 posts from a particular location ID (a specific time window for a specific user, hashtag, or reply context), but this number can be larger with healing and eventual consistency delays. Bundles are signed by the serving peer.

Fetching a user's timeline means traversing the bucket hierarchy recursively: start with the monthly bucket, drill into the weekly, daily, hourly, and finer buckets where posts exist. The RecursiveBucketVisitor handles this with a callback that decides at each level whether to recurse into finer granularity or skip, allowing efficient pagination even across sparse timelines.

Source: encoded_post_bundle.rs, hashiverse-lib/src/client/timeline/

Bucket types

Four (at the time of writing) bucket types determine how posts are indexed and discovered:

A single post may appear in multiple buckets simultaneously, each encrypted with the appropriate passphrase for that bucket.

Source: buckets.rs

Storage

On the server, post bundles are persisted to disk, while their metadata is persisted to fjall — a pure-Rust embedded key-value store with automatic compaction and good write throughput. fjall was chosen over redb and sled for its maintenance track record and operational simplicity. Posts are stored as Brotli-compressed, encrypted bytes.

On the browser client, posts are cached in IndexedDB via indexed_db_futures, with an in-memory stub for testing. Each category of client-side data is encrypted at rest with a different key, chosen to match its access model.

Source: hashiverse-server/src/environment/, wasm_client_storage.rs