Protocole des publications

Une publication dans Hashiverse est du HTML — rédigé dans le navigateur avec un éditeur de texte enrichi, assaini par DOMPurify avant affichage. Entre la rédaction et l'affichage se trouve un pipeline de compression, chiffrement, signature, soumission en deux phases, routage DHT et agrégation en bundles. Cette page suit une publication à travers ce pipeline.

La publication encodée

EncodedPostV1 est le format réseau d'une publication unique. Il contient :

Le chiffrement utilise plusieurs phrases de passe — une par contexte dans lequel apparaît la publication — via un schéma multi-clés personnalisé inspiré du format de chiffrement age. Une publication à la fois dans le fil d'un utilisateur et sous un hashtag est chiffrée avec, comme phrases de passe, l'identifiant public de l'utilisateur et la chaîne du hashtag. L'une ou l'autre clé permet de la déchiffrer. Les serveurs qui détiennent les octets chiffrés ne peuvent lire ni l'une ni l'autre.

Soumission : Claim puis Commit

La soumission d'une publication suit un protocole en deux phases :

  1. SubmitClaim : le client envoie une requête de claim avec une PoW. Le serveur valide la PoW et, si l'identifiant de publication n'est pas déjà présent, émet un jeton autorisant le commit.
  2. SubmitCommit : le client envoie les octets effectifs de la publication avec le jeton. Le serveur stocke la publication et renvoie une signature de confirmation.

Séparer le claim du commit prévient les attaques par inondation de charge utile : un serveur peut rejeter à peu de frais des claims de mauvaise foi (il suffit de vérifier la PoW) avant d'accepter une charge utile importante. La signature de confirmation du serveur est la preuve, pour le client, que la publication a été acceptée.

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

Bundles et buckets

Les publications ne sont ni stockées ni récupérées individuellement — elles sont regroupées en objets EncodedPostBundleV1, un bundle par bucket par serveur. Un bundle contient environ 20 publications d'un identifiant d'emplacement particulier (une fenêtre temporelle spécifique pour un utilisateur, un hashtag ou un contexte de réponse précis), mais ce nombre peut être plus élevé en raison de la réparation et des délais de cohérence éventuelle. Les bundles sont signés par le pair qui sert.

Récupérer le fil d'un utilisateur signifie parcourir la hiérarchie de buckets de manière récursive : commencer par le bucket mensuel, descendre dans le hebdomadaire, le quotidien, l'horaire et les buckets plus fins là où des publications existent. Le RecursiveBucketVisitor gère cela avec un callback qui décide à chaque niveau s'il faut descendre vers une granularité plus fine ou sauter, permettant une pagination efficace même à travers des fils éparses.

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

Types de buckets

Quatre (au moment de l'écriture) types de buckets déterminent comment les publications sont indexées et découvertes :

Une même publication peut apparaître simultanément dans plusieurs buckets, chacune chiffrée avec la phrase de passe appropriée à ce bucket.

Source : buckets.rs

Stockage

Côté serveur, les bundles de publications sont persistés sur disque, tandis que leurs métadonnées sont persistées dans fjall — un magasin clé-valeur embarqué pur Rust avec compactage automatique et bon débit en écriture. fjall a été choisi plutôt que redb et sled pour son historique de maintenance et sa simplicité opérationnelle. Les publications sont stockées sous forme d'octets compressés Brotli et chiffrés.

Côté client navigateur, les publications sont mises en cache dans IndexedDB via indexed_db_futures, avec un stub en mémoire pour les tests. Chaque catégorie de données côté client est chiffrée au repos avec une clé différente, choisie pour correspondre à son modèle d'accès.

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