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 :
- header : octets de la clé de vérification, octets d'engagement post-quantique, timestamp, longueur de la publication, et
linked_base_ids— références à des publications antérieures auxquelles celle-ci répond ou sur lesquelles elle s'appuie. - signature : signature Ed25519 (ou post-quantique) sur les hachages d'en-tête et de publication.
- post : le contenu HTML, compressé en Brotli et chiffré.
- post_id : hachage Blake3 de la signature — une adresse de contenu stable et vérifiable.
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 :
- 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.
- 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 :
- User (0) : le fil personnel de l'auteur
- Hashtag (1) : les publications contenant un hashtag donné
- Mention (2) : les publications mentionnant un utilisateur précis
- ReplyToPost (3) : les publications répondant à une publication précise
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