consolidate docs, add security poc
This commit is contained in:
parent
1f5867890d
commit
4999095bb3
128
doc/guide/guide-DE.md
Normal file
128
doc/guide/guide-DE.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# Frühe A Dokumente
|
||||||
|
|
||||||
|
# Bitte nicht öffentlich teilen
|
||||||
|
|
||||||
|
# Veilid Architektur Leitfaden
|
||||||
|
|
||||||
|
- [Aus der Umlaufbahn](#aus-der-umlaufbahn)
|
||||||
|
- [Aus der Vogelperspektive](#vogelperspektive)
|
||||||
|
- [Peer Netzwerk zum Datenspeichern](#peer_netzwerk_zur_datenspeicherung)
|
||||||
|
- [Block Speicher](#block-speicher)
|
||||||
|
- [Key-Value Speicher](#key-value-speicher)
|
||||||
|
- [Datenstrukturierung](#datenstrukturierung)
|
||||||
|
- [Peer- und Benutzeridentät](#peer-und-benutzeridentität)
|
||||||
|
- [Am Boden](#am_boden)
|
||||||
|
- [Peer Netzwerk im Detail](#peer-netzwerk-im-detail)
|
||||||
|
- [Benutzerprivatsphäre](#benutzerprivatsphäre)
|
||||||
|
- [Block Speicher im Detaail](#block-store-im-detail)
|
||||||
|
- [Key-Value Speicher im Detail](#key-value-speicher-im-detail)
|
||||||
|
|
||||||
|
## Aus der Umlaufbahn
|
||||||
|
|
||||||
|
Als Erstes wird die Frage behandelt "Was ist Veiled?". Die allgemeinste Beschreibung ist, dass Veilid ein Peer-to-Peer-Netzwerk zum Teilen von verschiedenen Arten von Daten ist.
|
||||||
|
|
||||||
|
Veilid wurde mit der Idee im Hinterkopf entworfen, dass jeder Benutzer seine eigenen Inhalte im Netzwerk speichern kann. Aber es ist auch möglich diese mit anderen ausgewählten Leuten zu teilen oder (wenn gewollt) auch mit gesamten Rest der Welt.
|
||||||
|
|
||||||
|
Der primäre Zweck des Veild Netzwerks ist es Infrastruktur für eine besondere Art von geteilten Daten zur Verfügung zu stellen: Social Medien in verschiedensten Arten. Dies umfasst leichtgewichtige Inhalte wie Twitters/Xs Tweets oder Mastodons Toots, mittleschwere Inhalte wie Bilder oder Lieder und schwergewichtige Inhalte wie Videos. Es ist eben so beabsichtigt Meta-Inhalte (wie persönliche Feeds, Antworten, private Nachrichten und so weiter) auf Basis von Veilid laufen zu lassen.
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
## Vogelperspektive
|
||||||
|
|
||||||
|
Nachdem wir nun wissen was Veilid ist und was in Veilid abgelegt werden sollte, ist es nun an der Zeit die Teile zu behandeln, die erklären wie Veilid dies ermöglicht. Natürlich nicht super detailiert (das kommt später), sondern her auf eine mittleren Detailgrad so dass alles auf einmal zur gleichen Zeit vom Leser verstanden werden kann.
|
||||||
|
|
||||||
|
### Peer Netzwerk zur Datenspeicherung
|
||||||
|
|
||||||
|
Auf unterster Ebene ist Veilid ein Netzwerk aus "Peers", die miteinander über das Internet kommunizieren. Peers senden sich gegenseitig Nachrichten (sogenannte Remote Procedure Calls) bezüglich der im Netzwerk ge- bzw. zu speichernden Daten und auch Nachrichten über das Netzwerk selbst. Zum Beispiel, kann eine Peer einen Anderen nach einer Datei fragen oder nach Informationen fragen, welche anderen Peers in Netzwerk bekannt sind/existieren.
|
||||||
|
|
||||||
|
Die Daten, die im Netzwerk gespeichert sind, werden in zwei Arten von Daten unterteilt: Datei-artige Daten, die üblicherweise groß sind und textartige Daten, die üblicherweise klein sind. Jede Art wird in einem eigenen Untersystem gespeichert, dass das so gestaltet wurde, dass es für diese Art von Daten optimal ist.
|
||||||
|
|
||||||
|
### Block Speicher
|
||||||
|
|
||||||
|
Datei-artige Inhalte werden in einem inhaltsaddressierbaren Block Speicher abgelegt. Jeder Block ist einfach ein Haufen (Blob) aus irgendwelchen Daten (z.B. ein JPEG oder ein MP4) beliebiger Größe. Die Prüfsumme (Hash) eines Block dient als eindeutige ID für diesen Block und kann von anderen Peers benutzt werden, um diesen Block abzufragen. Technisch gesehen können auch textuelle Daten als Block gespeichert werden und das sollte genau dann passieren, wenn die textuelle Daten als eine Art Dokument oder Datei eines bestimmten Typs verstanden werden.
|
||||||
|
|
||||||
|
### Key-Value Speicher
|
||||||
|
|
||||||
|
Kleinere und kurzlebigere textuelle Inhalte werden in einem Key-Value Speicher gespeichert (KV Speicher).Sachen wie z.B. Status Updates, Blogeinträge oder Benutzerbeschreibungen usw. sind alle dafür gedachte in diesem Teil des Datenspeichers gespeichert zu werden. KV Speicher Daten sind nicht einfach "im Veild Netzwerk", sondern gehören Benutzern und (werden auch von diesen gesteuert/kontrolliert). Sie werden über einen beliebigen vom Eigentümer der Daten gewählten Namen identifiziert. Jede Gruppe von Benutzern kann Daten hinzufügen, aber man kann nur die Daten ändern, die man selbst hinzugefügt hat.
|
||||||
|
|
||||||
|
Nehmen wir 2 beispielhafte Benutzer Boone und Marquette: Boones Benutzerbeschreibung und ihren Blogpost mit dem Titel "Hi, ich bin Bonne!" sind 2 Sachen, die dem selben Benutzer gehören, aber unterschiedliche ID haben. Boones Benutzerbeschreibung und Marquettes Benutzerbeschreibung sind 2 Sachen, die zwei unterschiedlichen Benutzer gehören, aber dieselbe ID haben.
|
||||||
|
|
||||||
|
KV Speicher Daten sind zustandsbehaftet, so dass Änderungen an ihnen vorgenommen werden könen. Boones Benutzerbeschreibung z.B. wird sicher nicht unverändert bleiben, sondern sich eher im Laufe der Zeit ändern, wenn er z.B. den Job wechselt oder sich neue Hobbies aneignet usw.. Die Zustandsbehaftung zusammen mit den beliebigen nutzer-definierten Identifiern (anstatt Prüfsummen auf Inhalte (Content Hashes)) führt dazu,dass man Boones Benutzerbeschreibung als abstrakte Sache betrachten kann und sich für Update auf diese registrieren kann.
|
||||||
|
|
||||||
|
### Datenstrukturierung
|
||||||
|
|
||||||
|
Mit der Verbindung aus Block Speicher und Key_Value Speicher ist es mögliche auch komplexe Konzepte zu kombinieren. Ein Song könnte z.B. an zwei Orten in Veilid verteilt abgelegt sein. Der Block Speicher würde dabei die Rohdaten speichern und der Key-Value Speicher würde eine Beschreibung zur Idee des Song abspeichern. Diese wäre vielleicht in Form eines JSON Objekts mit Metadaten über den Song wie dem Titel, den Komponisten, das Datum, die Codierungsinformationen usw. ebenso wie die ID der Daten im Block Speicher. Wir können dann auch verschiedene Versionen der JSON Daten abspeichern, wenn das Stück aktualisiert, verbessert, überarbeitet oder was auch immer wird. Jede Version würde auf einen anderen Block im Block Speicher verweisen. Es ist immer noch "derselbe Song" aus konzeptueller Sicht und somit hat er auch dieselbe ID im KV Speicher, aber die Rohbits, die damit verbunden sind, sind für jede Version unterschiedlich.
|
||||||
|
|
||||||
|
Ein anderes Beispiel (wenn auch mit einer noch etwas schwächeren Verbindung zum Block Speicher) wäre die Beschreibung eines Profilbildes. "Marquettes Profilbild" ist eine sehr abstrakte Beschreibung und genau genommen sind können sich die Bits, die damit zusammenhängen, im Laufe der Zeit stark variieren. So könnte es nicht nur verschiedene Versionen des Bildes, sondern auch komplett andere Bilder geben. Vielleicht ist es an einem Tage eine Foto von Marquette und am nächsten Tag ist es das Foto einer Blume.
|
||||||
|
|
||||||
|
In Soziale Medien finden sich viele Beispiel für solche Konzepte: Freundeslisten, Blocklisten, Indizes von Postings und Favoriten-Listen. Die sind alle zustandsbehaftete Beschreibungen einer bestimmten Art: Eine stabile Referenz auf eine Sache, aber der genaue Inhalt der Sache verändert sich im Laufe der Zeit. Das ist genau das, was wir im KV Speicher ablegen wollen und sich somit vom Block Store abgrenzt, auch wenn diese Daten auf die Inhalte des Block Store referenzieren.
|
||||||
|
|
||||||
|
### Peer- und Benutzeridentität
|
||||||
|
|
||||||
|
Es gibt zwei Darstellungen von Identitäten im Netzwerk: Die Peer- und die Benutzeridentität. Die Peeridentität ist einfach genug: Jeder Peer hat ein kryptografisches Schlüsselpaar, das er benutzt, um mit anderen Peers sicher zu kommunizieren und zwar für beides: Traditionelle verschlüsselte Kommuniktion und auch durch verschiedene verschlüsselte Routen. Die Peeridentität ist nur die ID einer bestimmten Instanz der Veilid Software, die auf einem Computer läuft.
|
||||||
|
|
||||||
|
Die Benutzeridentität wird deutlich umfassender genutzt. Benutzer (also Leute) wollen auf das Veilid Netzwerk zugreifen, so dass sie eine konsistente Identität über Geräte und Apps/Programme hinweg haben. Da aber Veilid keine Server in traditionellen Sinne hat, können wir nicht auf das normale Konzept von Benutzer-"Konten" zurückgreifen. Würde man das tun, würden Zentralisierungsspunkte im Netzwerk eingeführt werden, die in klassischen System oft die Quelle von Problemen gewesen sind. Viele Mastodon Benutzer habe sich schon in schwierigen Situationen befunden, wenn Ihre Instanzen Schwierigkeiten mit Ihren Sysadmin hatten und diese dann plötzlich die Instanzen abschalteten ohne vorher genug zu warnen.
|
||||||
|
|
||||||
|
Um diese Re-Zentralisierung von Identitäten zu vermeiden, nutzen wir kryptografische Identitäten auch für alle Benutzer. Das Schlüsselpaar den Benutzers wird zum Signieren und Verschlüsseln ihrer Inhalte benutzt, wie es notwendig ist, wenn man Inhalte auf den Datenspeicher veröffentlichen will. Ein Benutzer wird als "in seine Client App/Anwendung eingeloggt" bezeichnet, wenn die App/Anwendung eine Kopie seines privaten Schlüssels hat. Wenn man in eine Client App eingeloggt, dann verhält sie sich wie jede andere der Client Apps des Benutzers und ermöglicht es ihm Inhalte zu ent- und verschlüsseln,Nachrichten zu signieren und so weiter. Schlüssel können in neue Apps eingefügt werden, um sich in diese einzuloggen. Dies erlaubt dem Benutzer so viele Client Apps (auf jeder beliebigen Anzahl an Geräten zu haben) wie sie haben wollen.
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
## Am Boden
|
||||||
|
|
||||||
|
Die Vogelperspektive macht es möglich alles auf einmal im Kopf zu behalten, dafür lässt sie aber viele Implementierungsdetails weg. Deswegen ist es jetzt an der Zeit auf den "Boden" zurückzukehren und sich die Hände schmutzig zu machen. Grundsätzlich sollten es genug Informationen sein, um eine System ähnliche wie Veilid zu implementieren (mit der Ausnahmen von spezifischen Details der APIs und Datenformate). Dieser Anschnitt enthält keinen Code. Er ist keine Dokumentation des Codes selber, sondern der Kern eine Whitepapers.
|
||||||
|
|
||||||
|
### Peer Netwerk im Detail
|
||||||
|
|
||||||
|
Lasst uns als Erstes das Peer Netzwerk ansehen, weil seine Struktur die Basis für den Rest des Datenspeicherungsansatz bildet. Veilids Peer Netzwerk ist in der Art ähnlich zu anderen Peer to Peer Systemen, dass es sich von oben auf auf andere Protokolle legt und diese überlagert. Veilid versucht auf seine Art protokoll-agnostisch zu sein und ist aktuell entworfen um TCP, UDP, WebSocket und WebRTC sowie verschiedene Methoden zu Überwindung von NATs zu nutzen, so dass Veilid Peers auch Smartphones oder Computer bei unvertrauenswürdigen Internetanbietern und Ähnlichem seien können. Um das sicher zu stellen werden Peers nicht über eine Netzwerk Identität wie IP Adressen identifiziert, sondern über eine kryptografische Schlüsselpaar, das durch den Peer festgelegt wird. Jeder Peer veröffentlicht/bewirbt eine Menge von Optionen wie man mit Ihm kommunizieren kann. Diese nennt sich Dial Info und wenn ein Peer mit einem Anderen sprechen will, dann besorgt er sich die Dial Info von dem Peer aus dem Netzwerk und nutzt diese um zu kommunizieren.
|
||||||
|
|
||||||
|
Wenn sich ein Peer erstmalig mit Veilid verbindet, dann macht er dass in dem er die "Bootstrap Nodes" kontaktiert, die einfache IP Adressen Dial Infos haben, für die durch die Netzwerk Maintainer garantiert wird, dass diese (zeit-) stabil sind. Diese Bootstrap-Peers sind die ersten Einträge in der Routing Tabelle des Peers. Die Routing Tabelle ist ein sortiertes Adressbuch mit dem man feststellen kann, wie man mit einem Peer sprechen kann.
|
||||||
|
|
||||||
|
Die Routing Tabelle besteht aus einer Zuordnung öffentlicher Schlüssel der Peers zu einer nach Priorität sortierten Auswahl von Dial Infos. Um die Routing Tabelle zu befüllen, fragt der Peer andere Peers was seine Nachbarn im Netzwerk sind. Die Bezeichnung "Nachbar" ist hier über ein Ähnlichkeitsmaß bezüglich der Peer IDs definiert (genauer gesagt dem XOR Maß das viele Verteilte Hash Tables (DHTs) nutzen). Im Verlauf der Interaktionen mit dem dem Netzwerk wird der Peer die Dail Infos aktualisieren, wenn er Veränderungen feststellt. Ebenso kann er auch Dail Info für Peers abhängig von Ihrer Peer ID hinzufügen, die er im Verlauf entdeckt.
|
||||||
|
|
||||||
|
Um mit einen bestimmten Peer zu sprechen wird seine Dail Info in der Routing Tabelle nachgeschlagen. Wenn es eine Dail Info gibt, dann werden die Optionen in der durch die Routing Tabelle festgelegten Priorisierungsreihenfolge durchprobiert. Falls es die Dail Info nicht gibt, dann muss der Peer die Dail Info aus den Netzwerk anfragen. Dabei schaut er in seine Routing Tabelle, um den Peer zu finden der entsprechend der XOR Maß der nächstgelegene zum Zielpeer ist und schickt ihm einen RPC Aufruf mit dem Namen "find-node". Für jede gegebene Peer ID antwortet der Empfänger des "find-node" auf Rufs mit den Dial Infos der Peers in seiner Routing Tabelle, die der ID am nächsten sind. Die bringt den Peer näher an sein Ziel und zumindest in die Richtung des Peers nach dem er fragt. Wenn die Info des gewünschten Peers in der Antwort des Aufrufs enthalten war, dann ist der Vorgang beendet, sonst sendet er weiter "find-node" Aufrufe um dem gewünschten Zielpeer näher zu kommen. So bahnt er sich seinen Weg und versucht verschiedene alternative Peers (falls notwendig), in dem er den Nächsten als Erstes fragt, bis er entweder die gewünschte Dail Info findet oder das gesamte Netzwerk durchsucht hat oder den Vorgang abbricht.
|
||||||
|
|
||||||
|
### Benutzerprivatsphäre
|
||||||
|
Um sicherzustellen, dass Benutzer mit ein gewissen Maß an Privatsphäre in Veilid teilnehmen können, muss man sich um die Herausforderung kümmern, dass wenn man sich mit Veilid verbindet folglich auch mit anderen Nodes kommuniziert und somit IP Adressen teilt. Der Peer eines Benutzers wird deshalb regelmäßig RPC-Aufrufe erstellen, die die Identifikationsinformationen des Benutzer mit den IDs seiner Peers in Zusammenhang bringt. Veilid ermöglicht Privatsphäre durch die Nutzung einer RPC-Weiterleitungsmechanismus, der Kryptographie vergleichbar mit so genanntem "Onion Routing" nutzt. Hierbei wird der Kommunikationspfad zwischen dem tatsächlich ursprünglichen sendenden Peer und des schlussendlich final empfangenden Peer versteckt, in dem man über mehrere dazwischen liegenden Peers springt.
|
||||||
|
|
||||||
|
Der spezifisches Ansatz den Veilid bezüglich Privatsphäre hat ist zweiseitig: Privatsphäre des Senders und Privatsphäre des Empfängers. Jeweils einer oder beide könnten sich Privatsphäre wünschen oder für sich ausschließen. Um die Privatsphäre des Sender sicherzustellen nutzt Veilid etwas das sich „Safety Route" nennt: Eine Sequenz Peers beliebiger Größer, die durch den Sender ausgewählt wird, der die Nachricht sendet. Die Sequenz von Adressen werden (wie bei einer Matryoshka Puppe) verschlüsselt ineinander geschachtelt, so dass jeder Hop den Vorherigen und den Nächsten sehen kann, aber kein Hop die gesamte Route sehen kann. Dies ist ähnliche zu einer TOR (The Onion Router) Route, mit dem Unterschied, dass nur die Adressen für jeden Hop verschlüsselt sind. Die Route kann für jede Nachricht, die geschickt wird, zufällig gewählt werden.
|
||||||
|
|
||||||
|
Die Privatsphäre des Empfängers ist sofern ähnlich als das es dort auch eine Schachtelung von verschlüsselten Adressen gibt, aber mit dem Unterschied, dass die verschiedenen Adressen vorab geteilt worden sein müssen, weil es um eingehende Nachrichten geht. Diese werden „Private Routen“ genannt und sie werden als Teil der öffentlichen Daten eine Benutzers im KV Speicher veröffentlicht. Um volle Privatsphäre an beiden Enden sicherzustellen, wird eine Private Route als endgültiges Ziel einer Safety Route genutzt und die gesamt Route ist die Zusammensetzung aus beiden, so dass weder der Sender noch der Empfänger die IP adressiere des Anderen wissen.
|
||||||
|
|
||||||
|
Jeder Peer im Hop (einschließlich des initialen Peers) sendet einen "route" RPC Aufruf an den nächsten Peer im Hop, mit dem Rest der Gesamtroute (safety + private), die mit den Daten in der Kette weitergereicht wird. Der letzte Peer entschlüsselt den Rest der Route, die dann leer ist und kann sich dann den weitergeleiteten RPC ansehen und dann entsprechend diesem handeln. Der RPC selber muss nicht verschlüsselt sein, aber es ist gute Praxis diesen für den finalen Peer zu verschlüsseln, so dass die Peers dazwischen den User nicht durch Analyse des Datenverkehrs de-anonymisieren können.
|
||||||
|
|
||||||
|
Nimm bitte zur Kenntnis, dass die Routen benutzerorientiert sind. Sie sollten so verstanden werden, dass sie einen Möglichkeit darstellen mit einem bestimmten Benutzer-Peer zu sprechen, wo auch immer dieser ist. Jeder Peer in dieser Abfolge muss die tatsächlichen IP Adressen der Peers wissen, sonst können sie nicht kommunizieren. Aber die Savety und die Private Routes machen es schwer die Identität des Benutzer mit der Identität seiner Peers zusammenzubringen. Du weißt nur, dass der Nutzer irgendwo im Netzwerk ist, aber Du weißt nicht welche seine Adresse ist, auch wenn Du die Dail Infos seiner Peers in der Routing Tabelle gespeichert hast.
|
||||||
|
|
||||||
|
### Block Speicher im Detail
|
||||||
|
|
||||||
|
Wie bereits in der Vogelperspektive erwähnt ist es das Ziel des Blockspeichers inhaltsadressierbare Blocks von Daten zu speichern. Wie viele andere Peer to Peer Systeme zum Speichern von Daten nutzt auch Veilid verteilte Hash Tabellen (DHTs) als Kern des Block Speichers. Die Blockspeicher DHT hat als Schlüssel BLAKE3 Prüfsummen der Block Inhalte. Für jeden Schlüssel hält die DHT eine Liste von Peer IDs vor, die im Netzwerk deklariert wurden, dass sie den Block bereitstellen können.
|
||||||
|
|
||||||
|
Wenn ein Peer den Block bereitstellen möchte, dann macht einer einen "supply_block" RPC Aufruf mit der ID des Blocks in das Netzwerk. Der Empfänger des Calls kann dann die Informationen speichern, die der Peer bezüglich des vorgesehenen Block bereitstellt (wenn er will). Er kann auch andere Peers zurückgeben, die näher an der Block ID dran sind, die auch die Information speichern sollte. Die Peers stellen abhängig davon wie nah sie an der Block ID sind fest, ob sie die Information speichern oder nicht. Es kann auch entscheiden werden den Block zwischenzuspeichern, um sich selbst als Anbieter zu deklarieren.
|
||||||
|
|
||||||
|
So bereitgestellte Datensätze sind möglicherweise vergänglich, weil Peers das Netzwerk verlassen und somit ihre Informationen nicht mehr verfügbar sind. Deswegen wird jeder Peer, der einen Block bereitstellen will regelmäßig "supply_block" Nachrichten senden, um den Datensatz aktuell zu halten. Peers, die den Block zwischenspeichern, entscheiden an Hand der Popularität, des Speicherplatzes, der Bandbreite usw., die er übrig hat, wann das Zwischenspeichern endet.
|
||||||
|
|
||||||
|
Um einen Block zu empfangen, der im Block Speicher gespeichert wurde, sendet ein Peer den "find_block" RPC Aufruf. Der Empfänger wird dann entweder den Block zurücksenden oder möglicherweise auch eine Liste von Anbietern für den Block zurücksenden, die er kennt oder er liefert eine Liste von Peers zurück, die näher an dem Block liegen als er selbst.
|
||||||
|
|
||||||
|
Anders als bei BitTorrent sind Blocks nicht zwangsläufig Teil einer größeren Datei. Ein Block kann einfach eine einzelne Datei sein und dies wird oft der Fall für kleine Dateien sein. Größere Dateien können in kleinere Blocks aufgeteilt werden. In diesem Fall wird eine zusätzlicher Block mit einer Liste aus Block-Komponenten im Block Speicher gespeichert. Veilid selbst wird diese Block wie alle anderen Blocks behandeln und es gibt keinen eingebauten Mechanismus der festlegt, welchen Block man als erstes heruntergeladen oder teilen muss usw. (wie es sie bei BitTorrent gibt). Solche Features wäre dann abhängig von der Peer Software zu implementieren und können variieren. Verschiedene Clients werden auch die Möglichkeit haben zu entscheiden wie sie solche Block-Komponenten herunterladen wollen (z.B. automatisch, auf Eingabe des User oder etwas Anderes).
|
||||||
|
|
||||||
|
Der Mechanismus (Blocks zu haben die auf andere Blocks verweisen) ermöglicht es auch die Nutzung von IPFS-artige DAGs mit hierarchischen Daten als einen möglichen Modus. Somit können ganze Verzeichnisstrukturen gespeichert werden (und nicht nur Dateien). Allerdings ist das (genau so wie die Subfile Blocks) kein eingebauter Teil von Veilid, sondern eher ein möglicher Verwendungsmodus. Wie sie dem Benutzer heruntergeladen und dargestellt werden, obliegt dem Client Programm.
|
||||||
|
|
||||||
|
### Key-Value Speicher im Detail
|
||||||
|
|
||||||
|
Der Key-Value Speicher ist eine DHT (ähnlich wie der Block Store). Allerdings statt inhaltsbezogener Hashes als Schlüssel, nutzt der KV Speicher Benutzer-IDs als Schlüssel (Achtung: _NICHT_ Peer IDs). Für einen gegebenen Key hat der KV Speicher eine hierarchische Key-Value Map, die im Prinzip beliebige Strings mit Werten verknüpft, die selber wiederum Nummern, Zeichenketten, ein Datum, Uhrzeiten oder andere Key-Value Maps sein können. Der spezifische Wert, der an einer ID des Users gespeichert wird, ist versioniert, so dass man bestimmte Schemata von Unterschlüsseln und Werten definieren kann, die dann entsprechend der unterschiedlichen Versionen vom Client anders behandelt werden.
|
||||||
|
|
||||||
|
Wenn ein Nutzer wünscht Daten im Bezug auf ihren Schlüssel zu speichern, dann senden sie einen "set_value" RPC Aufruf an die Peers deren IDs am nächsten gemäß XOR Metric zu Ihrer eigenen ID liegen. Der Wert der dem RPC Aufruf mitgegeben wird ist ein Einzelwert, so dass das Netzwerk sicherstellen kann, dass nur der vorgesehene Nutzer Daten in seinem Key speichert. Die Peers, die den RPC Aufruf empfangen, können andere Peer IDs näher am Key zurückliefern (und so weiter), ähnlich wie auch beim den "supply_block" calls des Block Speichers. Am Ende werden einige Peers die Daten speichern. Der Peer des Users sollte regelmäßig die gespeicherten Daten aktualisieren, um sicher zu gehen, dass sie dauerhaft gespeichert bleiben. Es ist auch gute Praxis für den eigenen Peer des Benutzers, dass er seine eigene Daten in einem Cache (Zwischenspeicher) vorhält, so dass Client Programme den Peer des eigenen Benutzer als autorisierte Quelle der aktuellsten Daten verwenden kann. Hierfür ist es aber notwendig eine Route zu veröffentlichen, die es erlaubt das andere Peers dem eigenen Peer des Benutzers Nachrichten schicken. Eine private Route reicht hierfür aus.
|
||||||
|
|
||||||
|
Der Abruf ist ähnlich wie der Abruf beim Block Store. Der gewünschte Key wird über eine "get_value" Call bereit gestellt, der einen Wert zurückliefern kann (oder eine Liste von Peers die näher am Key liegen). Am Ende werden die signieren Daten zurückgeliefert und der Empfänger kann verifizieren, dass diese tatsächlich dem spezifizierten Benutzer gehören in dem man die Signatur prüft.
|
||||||
|
|
||||||
|
Beim Speichern und Abrufen von Daten ist es nicht notwendig, dass der im RPC Aufruf bereitgestellte Key ausschließlich die Benutzer ID sein muss. Er kann eine Liste von Zeichenketten beinhalten, die als Pfad zu gespeicherten Daten beim Benutzer Key diene, um damit spezifische Updates oder Abrufe durchzuführen. Dadurch wird der Datenverkehr im Netzwerk minimiert, weil nur die jeweils relevante Information umherbewegt wird.
|
||||||
|
|
||||||
|
Der spezifische Inhalt des Benutzers Keys wird über das Protokoll bestimmt und im Besonderen von der Client Software. Frühe Versionen des Protokolls nutzen eine DHT-Schema-Version, die ein sehr einfaches Social-Network-orientiertes Schema definiert. Später Versionen werden mehr ein generischeres Schema ermöglichen, so dass Client Plug-ins reichhaltiger Informationen speichern und darstellen können.
|
||||||
|
|
||||||
|
Die zustandsbehaftete Natur des Key Value Speichers bedeutet, dass sich Werte mit der Zeit ändern werden und es müssen Maßnahmen ergriffen werden, um auch auf diese Veränderungen zu reagieren. Ein Abfragemechanismus könnte verwendet werden, um regelmäßig abzufragen, ob neue Werte vorliegen, aber das wird zu einer Menge unnötigem Datenverkehrs im Netzwerk führen. Um das zu vermeiden erlaubt es Veilid, dass Peer einen "watch_value" RPC Aufruf senden, der einen DHT Key (mit Unterkeys) als Argument enthält. Der Empfänger würde dann einen Datensatz speichern, dass der Sender des RPC benachrichtigt werden möchte, wenn der Empfänger nachfolgend einen "set_value" RPC Aufruf erhält. In diesem Fall sendet der Empfänger dem sendenden Peer einen "value_changed" RPC Aufruf um ihm den neuen Wert zu übermitteln. Wie auch bei andere RPC Calls, muss auch "watch_value" regelmäßig neu gesendet werden, um das "Abonnement" für den Wert zu verlängern. Zusätzlich kann er Peers näher am Key zurückgegeben oder andere Peers die erfolgreich abonniert haben und so mit auch als Quelle dienen können.
|
||||||
|
|
||||||
|
TODO: Wie vermeidet man das Replay Updates? Vielleicht über eine Sequenznummer einem signierten Patch?
|
||||||
|
|
||||||
|
|
||||||
|
## Anhang 1: Dial Info und Signaling
|
||||||
|
|
||||||
|
## Anhang 2: RPC Listing
|
28
doc/guide/guide.css
Normal file
28
doc/guide/guide.css
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
* {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
width: 500px;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-toc, .subsection-toc {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-name {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: monospace;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
255
doc/guide/guide.html
Normal file
255
doc/guide/guide.html
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>Veilid Architecture Guide</title>
|
||||||
|
<meta name="description" content="a guide to the architecture of Veilid">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<div style="font-family: monospace; font-size: 3em; font-weight: bold; background-color: red; color: white; padding: 0.5em;">
|
||||||
|
early α docs<br/>
|
||||||
|
please don't share publicly
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>Veilid Architecture Guide</h1>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<ul class="section-toc">
|
||||||
|
<li>
|
||||||
|
<a class="section-name" href="#from-orbit">From Orbit</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="section-name" href="#birds-eye-view">Bird's Eye View</a>
|
||||||
|
<ul class="subsection-toc">
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#peer-network-for-data-storage">Peer Network for Data Storage</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#block-store">Block Store</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#key-value-store">Key-Value Store</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#structuring-data">Structuring Data</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#peer-and-user-identity">Peer and User Identity</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="section-name" href="#on-the-ground">On The Ground</a>
|
||||||
|
<ul class="subsection-toc">
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#peer-network-revisited">Peer Network, Revisted</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#user-privacy">User Privacy</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#block-store-revisited">Block Store, Revisited</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="subsection-name" href="#key-value-store-revisited">Key-Value Store, Revisited</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h2 id="from-orbit">From Orbit</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The first matter to address is the question "What is Veilid?" The highest-level description is that Veilid is a peer-to-peer network for easily sharing various kinds of data.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Veilid is designed with a social dimension in mind, so that each user can have their personal content stored on the network, but also can share that content with other people of their choosing, or with the entire world if they want.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The primary purpose of the Veilid network is to provide the infrastructure for a specific kind of shared data: social media in various forms. That includes light-weight content such as Twitter's tweets or Mastodon's toots, medium-weight content like images and songs, and heavy-weight content like videos. Meta-content such as personal feeds, replies, private messages, and so forth are also intended to run atop Veilid.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h2 id="birds-eye-view">Bird's Eye View</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Now that we know what Veilid is and what we intend to put on it, the second order of business is to address the parts of the question of how Veilid achieves that. Not at a very detailed level, of course, that will come later, but rather at a middle level of detail such that all of it can fit in your head at the same time.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="peer-network-for-data-storage">Peer Network for Data Storage</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The bottom-most level of Veilid is a network of peers communicating to one another over the internet. Peers send each other messages (remote procedure calls) about the data being stored on the network, and also messages about the network itself. For instance, one peer might ask another for some file, or it might ask for info about what other peers exist in the network.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The data stored in the network is segmented into two kinds of data: file-like data, which typically is large, and textual data, which typically is small. Each kind of data is stored in its own subsystem specifically chosen to optimize for that kind of data.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="block-store">Block Store</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
File-like content is stored in a content-addressable block store. Each block is just some arbitrary blob of data (for instance, a JPEG or an MP4) of whatever size. The hash of that block acts as the unique identifier for the block, and can be used by peers to request particular blocks. Technically, textual data can be stored as a block as well, and this is expected to be done when the textual data is thought of as a document or file of some sort.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="key-value-store">Key-Value Store</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Smaller, more ephemeral textual content generally, however, is stored in a key-value-store (KV store). Things like status updates, blog posts, user bios, etc. are all thought of as being suited for storage in this part of the data store. KV store data is not simply "on the Veilid network", but also owned/controlled by users, and identified by an arbitrary name chosen by the owner the data. Any group of users can add data, but can only change the data they've added.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
For instance, we might talk about Boone's bio vs. Boone's blogpost titled "Hi, I'm Boone!", which are two things owned by the same user but with different identifiers, or on Boone's bio vs. Marquette's bio, which are two things owned by distinct users but with the same identifier.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
KV store data is also stateful, so that updates to it can be made. Boone's bio, for instance, would not be fixed in time, but rather is likely to vary over time as he changes jobs, picks up new hobbies, etc. Statefulness, together with arbitrary user-chosen identifiers instead of content hashes, means that we can talk about "Boone's Bio" as an abstract thing, and subscribe to updates to it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="structuring-data">Structuring Data</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The combination of block storage and key-value storage together makes it possible to have higher-level concepts as well. A song, for instance, might be represented in two places in Veilid: the block store would hold the raw data, while the KV store would store a representation of the idea of the song. Maybe that would consist of a JSON object with metadata about the song, like the title, composer, date, encoding information, etc. as well as the ID of the block store data. We can then also store different <em>versions</em> of that JSON data, as the piece is updated, upsampled, remastered, or whatever, each one pointing to a different block in the block store. It's still "the same song", at a conceptual level, so it has the same identifier in the KV store, but the raw bits associated with each version differ.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Another example of this, but with even more tenuous connection between the block store data, is the notion of a profile picture. "Marquette's Profile Picture" is a really abstracted notion, and precisely which bits it corresponds to can vary wildly over time, not just being different versions of the picture but completely different pictures entirely. Maybe one day it's a photo of Marquette and the next day it's a photo of a flower.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Social media offers many examples of these concepts. Friends lists, block lists, post indexes, favorites. These are all stateful notions, in a sense: a stable reference to a thing, but the precise content of the thing changes over time. These are exactly what we would put in the KV store, as opposed to in the block store, even if this data makes reference to content in the block store.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="peer-and-user-identity">Peer and User Identity</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Two notions of identity are at play in the above network: peer identity and user identity. Peer identity is simple enough: each peer has a cryptographic key pair that it uses to communicate securely with other peers, both through traditional encrypted communication, and also through the various encrypted routes. Peer identity is just the identity of the particular instance of the Veilid software running on a computer.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
User identity is a slightly richer notion. Users, that is to say, <em>people</em>, will want to access the Veilid network in a way that has a consistent identity across devices and apps. But since Veilid doesn't have servers in any traditional sense, we can't have a normal notion of "account". Doing so would also introduce points of centralization, which federated systems have shown to be a source of trouble. Many Mastodon users have found themselves in a tricky situation when their instance sysadmins burned out and suddenly shut down the instance without enough warning.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To avoid this re-centralization of identity, we use cryptographic identity for users as well. The user's key pair is used to sign and encrypt their content as needed for publication to the data store. A user is said to be "logged in" to a client app whenever that app has a copy of their private key. When logged in a client app act like any other of the user's client apps, able to decrypt and encrypt content, sign messages, and so forth. Keys can be added to new apps to sign in on them, allowing the user to have any number of clients they want, on any number of devices they want.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<h2 id="on-the-ground">On The Ground</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The bird's eye view of things makes it possible to hold it all in mind at once, but leaves out lots of information about implementation choice. It's now time to come down to earth and get our hands dirty. In principl, this should be enough information to implement a system very much like Veilid, with the exception perhaps of the specific details of the APIs and data formats. This section won't have code, it's not documentation of the codebase, but rather is intended to form the meat of a whitepaper.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="peer-network-revisited">Peer Network, Revisited</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
First, let's look at the peer network, since its structure forms the basis for the remainder of the data storage approach. Veilid's peer network is similar to other peer-to-peer systems in that it's overlaid on top of other protocols. Veilid tries to be somewhat protocol-agnostic, however, and currently is designed to use TCP, UDP, WebSockets, and WebRTC, as well as various methods of traversing NATs so that Veilid peers can be smartphones, personal computers on hostile ISPs, etc. To facilitate this, peers are identified not by some network identity like an IP address, but instead by peer-chosen cryptographic key-pairs. Each peer also advertises a variety of options for how to communicate with it, called dial info, and when one peer wants to talk to another, it gets the dial info for that peer from the network and then uses it to communicate.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
When a peer first connects to Veilid, it does so by contacting bootstrap peers, which have simple IP address dial info that is guaranteed to be stable by the maintainers of the network. These bootstrap peers are the first entries in the peer's routing table -- an address book of sorts, which it uses to figure out how to talk to a peer. The routing table consists of a mapping from peer public keys to prioritized choices for dial info. To populate the routing table, the peer asks other peers what its neighbors are in the network. The notion of neighbor here is defined by a similarity metric on peer IDs, in particular an XOR metric like many DHTs use. Over the course of interacting with the network, the peer will keep dial info up to date when it detects changes. It may also add dial info for peers it discovers along the way, depending on the peer ID.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To talk to a specific peer, its dial info is looked up in the routing table. If there is dial info present, then the options are attempted in order of the priority specified in the routing table. Otherwise, the peer has to request the dial info from the network, so it looks through its routing table to find the peer who's ID is nearest the target peer according to the XOR metric, and sends it an RPC call with a procedure named <code>find_node</code>. Given any particular peer ID, the receiver of a <code>find_node</code> call returns dial info for the peers in its routing table that are nearest the given ID. This gets the peer closer to its destination, at least in the direction of the other peer it asked. If the desired peer's information was in the result of the call, then it's done, otherwise it calls <code>find_node</code> again to get closer. It iterates in this way, possibly trying alternate peers, as necessary, in a nearest-first fashion until it either finds the desire'd peer's dial info, has exhausted the entire network, or gives up.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="user-privacy">User Privacy</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In order to ensure that users can participate in Veilid with some amount of privacy, we need to address the fact that being connected to Veilid entails communicating with other peers, and therefore sharing IP addresses. A user's peer will therefore be frequently issuing RPCs in a way that directly associates the user's identifying information with their peer's ID. Veilid provides privacy by allowing the use of an RPC forwarding mechanism that uses cryptography to similar to onion routing in order to hide the path that a message takes between its actual originating peer and its actual destination peer, by hopping between additional intermediate peers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The specific approach that Veilid takes to privacy is two sided: privacy of the sender of a message, and privacy of the receiver of a message. Either or both sides can want privacy or opt out of privacy. To achieve sender privacy, Veilid use something called a Safety Route: a sequence of any number of peers, chosen by the sender, who will forward messages. The sequence of addresses is put into a nesting doll of encryption, so that each hop can see the previous and next hops, while no hop can see the whole route. This is similar to a Tor route, except only the addresses are encrypted for each hop. The route can be chosen at random for each message being sent.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Receiver privacy is similar, in that we have a nesting doll of encrypted peer addresses, except because it's for incoming messages, the various addresses have to be shared ahead of time. We call such things Private Routes, and they are published to the key-value store as part of a user's public data. For full privacy on both ends, a Private Route will be used as the final destination of a Safety Route, and the total route is the composition of the two, so that neither the sender nor receiver knows the IP address of the other.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Each peer in the hop, including the initial peer, sends a <code>route</code> RPC to the next peer in the hop, with the remainder of the full route (safety + private), forwarding the data along. The final peer decrypts the remainder of the route, which is now empty, and then can inspect the forwarded RPC to act on it. The RPC itself doesn't need to be encrypted, but it's good practice to encrypt it for the final receiving peer so that the intermediate peers can't de-anonymize the sending user from traffic analysis.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Note that the routes are <em>user</em> oriented. They should be understood as a way to talk to a particular <em>user's</em> peer, wherever that may be. Each peer of course has to know about the actual IP addresses of the peers, otherwise it couldn't communicate, but safety and private routes make it hard to associate the <em>user's</em> identity with their <em>peer's</em> identity. You know that the user is somewhere on the network, but you don't know which IP address is their's, even if you do in fact have their peer's dial info stored in the routing table.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="block-store-revisited">Block Store Revisited</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
As mentioned in the Bird's Eye View, the block store is intended to store content-addressed blocks of data. Like many other peer-to-peer systems for storing data, Veilid uses a distributed hash table as the core of the block store. The block store DHT has as keys BLAKE3 hashes of block content. For each key the DHT associates a list of peer IDs for peers that have declared to the network that they can supply the block.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If a peer wishes to supply the block, it makes a <code>supply_block</code> RPC call to the network with the id of the block. The receiver of the call can then store the information that the peer supplies the designated block if it wants, and also can return other peers nearer to the block's ID that should also store the information. Peers determine whether or not to store this information based on how close it is to the block's ID. It may also choose to cache the block, possibly also declaring itself to be a supplier as well.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Supplier records are potentially brittle because peers leave the network, making their information unavailable. Because of this, any peer that wishes to supply a block will periodically send <code>supply_block</code> messages to refresh the records. Peers that are caching blocks determine when to stop caching based on how popular a block is, how much space or bandwidth it can spare, etc.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To retrieve a block that has been stored in the blockstore, a peer makes a <code>find_block</code> RPC. The receiver will then either return the block, or possibly return a list of suppliers for the block that it knows about, or return a list of peers that are closer to the block.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Unlike BitTorrent, blocks are not inherently part of a larger file. A block can be just a single file, and often that will be the case for small files. Large files can be broken up into smaller blocks, however, and then an additional block with a list of those component blocks can be stored in the block store. Veilid itself, however, would treat this like any other block, and there are no built-in mechanisms for determining which blocks to download first, which to share first, etc. like there are in BitTorrent. These features would be dependent on the peer software's implementation and could vary. Different clients will also be able to decide how they want to download such "compound" blocks -- automatically, via a prompt to the user, or something else.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The mechanism of having blocks that refer to other blocks also enables IPFS-style DAGs of hierarchical data as one mode of use of the block store, allowing entire directory structures to be stored, not just files. However, as with sub-file blocks, this is not a built-in part of Veilid but rather a mode of use, and how they're downloaded and presented to the user is up to the client program.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="key-value-store-revisited">Key-Value Store, Revisited</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The key-value store is a DHT similar to the block store. However, rather than using content hashes as keys, the KV store uses user IDs as keys (note: <em>not</em> peer IDs). At a given key, the KV store has a hierarchical key-value map that associates in-principle arbitrary strings with values, which themselves can be numbers, strings, datetimes, or other key-value maps. The specific value stored in at a user's ID is versioned, so that particular schemas of subkeys and values can be defined and handled appropriately by different versions of clients.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
When a user wishes to store data under their key, they send a <code>set_value</code> RPC to the peer's whose IDs are closest by the XOR metric to their own user ID. The value provided to the RPC is a signed value, so that the network can ensure only the designated user is storing data at their key. The peers that receive the RPC may return other peer IDs closer to the key, and so on, similar to how the block store handles <code>supply_block</code> calls. Eventually, some peers will store the data. The user's own peer should periodically refresh the stored data, to ensure that it persists. It's also good practice for the user's own peer to cache the data, so that client programs can use the user's own peer as a canonical source of the most-up-to-date value, but doing so would require a route to be published that lets other peers send the user's own peer messages. A private route suffices for this.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Retrieval is similar to block store retrieval. The desired key is provided to a <code>get_value</code> call, which may return th value, or a list of other peers that are closer to the key. Eventually the signed data is returned, and the recipient can verify that it does indeed belong to the specified user by checking the signature.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
When storing and retrieving, the key provided to the RPCs is not required to be only the user's ID. It can include a list of strings which act as a path into the data stored at the user's key, targetting it specifically for update or retrieval. This lets the network minimize data transfer, because only the relevant information has to move around.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The specific content of the user's keys is determined partially by the protocol and partially by the client software. Early versions of the protocol use a DHT schema version that defines a fairly simple social network oriented schema. Later versions will enable a more generic schema so that client plugins can store and display richer information.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The stateful nature of the key-value store means that values will change over time, and actions may need to be taken in response to those changes. A polling mechanism could be used to periodically check for new values, but this will lead to lots of unnecessary traffic in the network, so to avoid this, Veilid allows peers to send <code>watch_value</code> RPCs, with a DHT key (with subkeys) as its argument. The receiver would then store a record that the sender of the RPC wants to be alerted when the receiver gets subsequent <code>set_value</code> calls, at which time the receiver sends the sending peer a <code>value_changed</code> RPC to push the new value. As with other RPC calls, <code>watch_value</code> needs to be periodically re-sent to refresh the subscription to the value. Additionally, also as with other calls, <code>watch_value</code> may not succeed on the receiver, which instead might return other peers closer to the value, or might return other peers that have successfully subscribed to the value and thus might act as a source for it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
TODO How to avoid replay updates?? maybe via a sequence number in the signed patch?
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Appendix 1: Dial Info and Signaling</h2>
|
||||||
|
|
||||||
|
<h2>Appendix 2: RPC Listing</h2
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
126
doc/guide/guide.md
Normal file
126
doc/guide/guide.md
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
# early α docs
|
||||||
|
|
||||||
|
# please don't share publicly
|
||||||
|
|
||||||
|
# Veilid Architecture Guide
|
||||||
|
|
||||||
|
- [From Orbit](#from-orbit)
|
||||||
|
- [Bird's Eye View](#birds-eye-view)
|
||||||
|
- [Peer Network for Data Storage](#peer-network-for-data-storage)
|
||||||
|
- [Block Store](#block-store)
|
||||||
|
- [Key-Value Store](#key-value-store)
|
||||||
|
- [Structuring Data](#structuring-data)
|
||||||
|
- [Peer and User Identity](#peer-and-user-identity)
|
||||||
|
- [On The Ground](#on-the-ground)
|
||||||
|
- [Peer Network, Revisited](#peer-network-revisited)
|
||||||
|
- [User Privacy](#user-privacy)
|
||||||
|
- [Block Store, Revisited](#block-store-revisited)
|
||||||
|
- [Key-Value Store, Revisited](#key-value-store-revisited)
|
||||||
|
|
||||||
|
## From Orbit
|
||||||
|
|
||||||
|
The first matter to address is the question "What is Veilid?" The highest-level description is that Veilid is a peer-to-peer network for easily sharing various kinds of data.
|
||||||
|
|
||||||
|
Veilid is designed with a social dimension in mind, so that each user can have their personal content stored on the network, but also can share that content with other people of their choosing, or with the entire world if they want.
|
||||||
|
|
||||||
|
The primary purpose of the Veilid network is to provide the infrastructure for a specific kind of shared data: social media in various forms. That includes light-weight content such as Twitter's tweets or Mastodon's toots, medium-weight content like images and songs, and heavy-weight content like videos. Meta-content such as personal feeds, replies, private messages, and so forth are also intended to run atop Veilid.
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
## Bird's Eye View
|
||||||
|
|
||||||
|
Now that we know what Veilid is and what we intend to put on it, the second order of business is to address the parts of the question of how Veilid achieves that. Not at a very detailed level, of course, that will come later, but rather at a middle level of detail such that all of it can fit in your head at the same time.
|
||||||
|
|
||||||
|
### Peer Network for Data Storage
|
||||||
|
|
||||||
|
The bottom-most level of Veilid is a network of peers communicating to one another over the internet. Peers send each other messages (remote procedure calls) about the data being stored on the network, and also messages about the network itself. For instance, one peer might ask another for some file, or it might ask for info about what other peers exist in the network.
|
||||||
|
|
||||||
|
The data stored in the network is segmented into two kinds of data: file-like data, which typically is large, and textual data, which typically is small. Each kind of data is stored in its own subsystem specifically chosen to optimize for that kind of data.
|
||||||
|
|
||||||
|
### Block Store
|
||||||
|
|
||||||
|
File-like content is stored in a content-addressable block store. Each block is just some arbitrary blob of data (for instance, a JPEG or an MP4) of whatever size. The hash of that block acts as the unique identifier for the block, and can be used by peers to request particular blocks. Technically, textual data can be stored as a block as well, and this is expected to be done when the textual data is thought of as a document or file of some sort.
|
||||||
|
|
||||||
|
### Key-Value Store
|
||||||
|
|
||||||
|
Smaller, more ephemeral textual content generally, however, is stored in a key-value-store (KV store). Things like status updates, blog posts, user bios, etc. are all thought of as being suited for storage in this part of the data store. KV store data is not simply "on the Veilid network", but also owned/controlled by users, and identified by an arbitrary name chosen by the owner the data. Any group of users can add data, but can only change the data they've added.
|
||||||
|
|
||||||
|
For instance, we might talk about Boone's bio vs. Boone's blogpost titled "Hi, I'm Boone!", which are two things owned by the same user but with different identifiers, or on Boone's bio vs. Marquette's bio, which are two things owned by distinct users but with the same identifier.
|
||||||
|
|
||||||
|
KV store data is also stateful, so that updates to it can be made. Boone's bio, for instance, would not be fixed in time, but rather is likely to vary over time as he changes jobs, picks up new hobbies, etc. Statefulness, together with arbitrary user-chosen identifiers instead of content hashes, means that we can talk about "Boone's Bio" as an abstract thing, and subscribe to updates to it.
|
||||||
|
|
||||||
|
### Structuring Data
|
||||||
|
|
||||||
|
The combination of block storage and key-value storage together makes it possible to have higher-level concepts as well. A song, for instance, might be represented in two places in Veilid: the block store would hold the raw data, while the KV store would store a representation of the idea of the song. Maybe that would consist of a JSON object with metadata about the song, like the title, composer, date, encoding information, etc. as well as the ID of the block store data. We can then also store different _versions_ of that JSON data, as the piece is updated, upsampled, remastered, or whatever, each one pointing to a different block in the block store. It's still "the same song", at a conceptual level, so it has the same identifier in the KV store, but the raw bits associated with each version differ.
|
||||||
|
|
||||||
|
Another example of this, but with even more tenuous connection between the block store data, is the notion of a profile picture. "Marquette's Profile Picture" is a really abstracted notion, and precisely which bits it corresponds to can vary wildly over time, not just being different versions of the picture but completely different pictures entirely. Maybe one day it's a photo of Marquette and the next day it's a photo of a flower.
|
||||||
|
|
||||||
|
Social media offers many examples of these concepts. Friends lists, block lists, post indexes, favorites. These are all stateful notions, in a sense: a stable reference to a thing, but the precise content of the thing changes over time. These are exactly what we would put in the KV store, as opposed to in the block store, even if this data makes reference to content in the block store.
|
||||||
|
|
||||||
|
### Peer and User Identity
|
||||||
|
|
||||||
|
Two notions of identity are at play in the above network: peer identity and user identity. Peer identity is simple enough: each peer has a cryptographic key pair that it uses to communicate securely with other peers, both through traditional encrypted communication, and also through the various encrypted routes. Peer identity is just the identity of the particular instance of the Veilid software running on a computer.
|
||||||
|
|
||||||
|
User identity is a slightly richer notion. Users, that is to say, _people_, will want to access the Veilid network in a way that has a consistent identity across devices and apps. But since Veilid doesn't have servers in any traditional sense, we can't have a normal notion of "account". Doing so would also introduce points of centralization, which federated systems have shown to be a source of trouble. Many Mastodon users have found themselves in a tricky situation when their instance sysadmins burned out and suddenly shut down the instance without enough warning.
|
||||||
|
|
||||||
|
To avoid this re-centralization of identity, we use cryptographic identity for users as well. The user's key pair is used to sign and encrypt their content as needed for publication to the data store. A user is said to be "logged in" to a client app whenever that app has a copy of their private key. When logged in a client app act like any other of the user's client apps, able to decrypt and encrypt content, sign messages, and so forth. Keys can be added to new apps to sign in on them, allowing the user to have any number of clients they want, on any number of devices they want.
|
||||||
|
|
||||||
|
* * *
|
||||||
|
|
||||||
|
## On The Ground
|
||||||
|
|
||||||
|
The bird's eye view of things makes it possible to hold it all in mind at once, but leaves out lots of information about implementation choice. It's now time to come down to earth and get our hands dirty. In principl, this should be enough information to implement a system very much like Veilid, with the exception perhaps of the specific details of the APIs and data formats. This section won't have code, it's not documentation of the codebase, but rather is intended to form the meat of a whitepaper.
|
||||||
|
|
||||||
|
### Peer Network, Revisited
|
||||||
|
|
||||||
|
First, let's look at the peer network, since its structure forms the basis for the remainder of the data storage approach. Veilid's peer network is similar to other peer-to-peer systems in that it's overlaid on top of other protocols. Veilid tries to be somewhat protocol-agnostic, however, and currently is designed to use TCP, UDP, WebSockets, and WebRTC, as well as various methods of traversing NATs so that Veilid peers can be smartphones, personal computers on hostile ISPs, etc. To facilitate this, peers are identified not by some network identity like an IP address, but instead by peer-chosen cryptographic key-pairs. Each peer also advertises a variety of options for how to communicate with it, called dial info, and when one peer wants to talk to another, it gets the dial info for that peer from the network and then uses it to communicate.
|
||||||
|
|
||||||
|
When a peer first connects to Veilid, it does so by contacting bootstrap peers, which have simple IP address dial info that is guaranteed to be stable by the maintainers of the network. These bootstrap peers are the first entries in the peer's routing table -- an address book of sorts, which it uses to figure out how to talk to a peer. The routing table consists of a mapping from peer public keys to prioritized choices for dial info. To populate the routing table, the peer asks other peers what its neighbors are in the network. The notion of neighbor here is defined by a similarity metric on peer IDs, in particular an XOR metric like many DHTs use. Over the course of interacting with the network, the peer will keep dial info up to date when it detects changes. It may also add dial info for peers it discovers along the way, depending on the peer ID.
|
||||||
|
|
||||||
|
To talk to a specific peer, its dial info is looked up in the routing table. If there is dial info present, then the options are attempted in order of the priority specified in the routing table. Otherwise, the peer has to request the dial info from the network, so it looks through its routing table to find the peer who's ID is nearest the target peer according to the XOR metric, and sends it an RPC call with a procedure named `find_node`. Given any particular peer ID, the receiver of a `find_node` call returns dial info for the peers in its routing table that are nearest the given ID. This gets the peer closer to its destination, at least in the direction of the other peer it asked. If the desired peer's information was in the result of the call, then it's done, otherwise it calls `find_node` again to get closer. It iterates in this way, possibly trying alternate peers, as necessary, in a nearest-first fashion until it either finds the desired peer's dial info, has exhausted the entire network, or gives up.
|
||||||
|
|
||||||
|
### User Privacy
|
||||||
|
|
||||||
|
In order to ensure that users can participate in Veilid with some amount of privacy, we need to address the fact that being connected to Veilid entails communicating with other peers, and therefore sharing IP addresses. A user's peer will therefore be frequently issuing RPCs in a way that directly associates the user's identifying information with their peer's ID. Veilid provides privacy by allowing the use of an RPC forwarding mechanism that uses cryptography to similar to onion routing in order to hide the path that a message takes between its actual originating peer and its actual destination peer, by hopping between additional intermediate peers.
|
||||||
|
|
||||||
|
The specific approach that Veilid takes to privacy is two sided: privacy of the sender of a message, and privacy of the receiver of a message. Either or both sides can want privacy or opt out of privacy. To achieve sender privacy, Veilid use something called a Safety Route: a sequence of any number of peers, chosen by the sender, who will forward messages. The sequence of addresses is put into a nesting doll of encryption, so that each hop can see the previous and next hops, while no hop can see the whole route. This is similar to a Tor route, except only the addresses are encrypted for each hop. The route can be chosen at random for each message being sent.
|
||||||
|
|
||||||
|
Receiver privacy is similar, in that we have a nesting doll of encrypted peer addresses, except because it's for incoming messages, the various addresses have to be shared ahead of time. We call such things Private Routes, and they are published to the key-value store as part of a user's public data. For full privacy on both ends, a Private Route will be used as the final destination of a Safety Route, and the total route is the composition of the two, so that neither the sender nor receiver knows the IP address of the other.
|
||||||
|
|
||||||
|
Each peer in the hop, including the initial peer, sends a `route` RPC to the next peer in the hop, with the remainder of the full route (safety + private), forwarding the data along. The final peer decrypts the remainder of the route, which is now empty, and then can inspect the forwarded RPC to act on it. The RPC itself doesn't need to be encrypted, but it's good practice to encrypt it for the final receiving peer so that the intermediate peers can't de-anonymize the sending user from traffic analysis.
|
||||||
|
|
||||||
|
Note that the routes are _user_ oriented. They should be understood as a way to talk to a particular _user's_ peer, wherever that may be. Each peer of course has to know about the actual IP addresses of the peers, otherwise it couldn't communicate, but safety and private routes make it hard to associate the _user's_ identity with their _peer's_ identity. You know that the user is somewhere on the network, but you don't know which IP address is theirs, even if you do in fact have their peer's dial info stored in the routing table.
|
||||||
|
|
||||||
|
### Block Store Revisited
|
||||||
|
|
||||||
|
As mentioned in the Bird's Eye View, the block store is intended to store content-addressed blocks of data. Like many other peer-to-peer systems for storing data, Veilid uses a distributed hash table as the core of the block store. The block store DHT has as keys BLAKE3 hashes of block content. For each key the DHT associates a list of peer IDs for peers that have declared to the network that they can supply the block.
|
||||||
|
|
||||||
|
If a peer wishes to supply the block, it makes a `supply_block` RPC call to the network with the id of the block. The receiver of the call can then store the information that the peer supplies the designated block if it wants, and also can return other peers nearer to the block's ID that should also store the information. Peers determine whether or not to store this information based on how close it is to the block's ID. It may also choose to cache the block, possibly also declaring itself to be a supplier as well.
|
||||||
|
|
||||||
|
Supplier records are potentially brittle because peers leave the network, making their information unavailable. Because of this, any peer that wishes to supply a block will periodically send `supply_block` messages to refresh the records. Peers that are caching blocks determine when to stop caching based on how popular a block is, how much space or bandwidth it can spare, etc.
|
||||||
|
|
||||||
|
To retrieve a block that has been stored in the blockstore, a peer makes a `find_block` RPC. The receiver will then either return the block, or possibly return a list of suppliers for the block that it knows about, or return a list of peers that are closer to the block.
|
||||||
|
|
||||||
|
Unlike BitTorrent, blocks are not inherently part of a larger file. A block can be just a single file, and often that will be the case for small files. Large files can be broken up into smaller blocks, however, and then an additional block with a list of those component blocks can be stored in the block store. Veilid itself, however, would treat this like any other block, and there are no built-in mechanisms for determining which blocks to download first, which to share first, etc. like there are in BitTorrent. These features would be dependent on the peer software's implementation and could vary. Different clients will also be able to decide how they want to download such "compound" blocks -- automatically, via a prompt to the user, or something else.
|
||||||
|
|
||||||
|
The mechanism of having blocks that refer to other blocks also enables IPFS-style DAGs of hierarchical data as one mode of use of the block store, allowing entire directory structures to be stored, not just files. However, as with sub-file blocks, this is not a built-in part of Veilid but rather a mode of use, and how they're downloaded and presented to the user is up to the client program.
|
||||||
|
|
||||||
|
### Key-Value Store, Revisited
|
||||||
|
|
||||||
|
The key-value store is a DHT similar to the block store. However, rather than using content hashes as keys, the KV store uses user IDs as keys (note: _not_ peer IDs). At a given key, the KV store has a hierarchical key-value map that associates in-principle arbitrary strings with values, which themselves can be numbers, strings, datetimes, or other key-value maps. The specific value stored in at a user's ID is versioned, so that particular schemas of subkeys and values can be defined and handled appropriately by different versions of clients.
|
||||||
|
|
||||||
|
When a user wishes to store data under their key, they send a `set_value` RPC to the peer's whose IDs are closest by the XOR metric to their own user ID. The value provided to the RPC is a signed value, so that the network can ensure only the designated user is storing data at their key. The peers that receive the RPC may return other peer IDs closer to the key, and so on, similar to how the block store handles `supply_block` calls. Eventually, some peers will store the data. The user's own peer should periodically refresh the stored data, to ensure that it persists. It's also good practice for the user's own peer to cache the data, so that client programs can use the user's own peer as a canonical source of the most-up-to-date value, but doing so would require a route to be published that lets other peers send the user's own peer messages. A private route suffices for this.
|
||||||
|
|
||||||
|
Retrieval is similar to block store retrieval. The desired key is provided to a `get_value` call, which may return the value, or a list of other peers that are closer to the key. Eventually the signed data is returned, and the recipient can verify that it does indeed belong to the specified user by checking the signature.
|
||||||
|
|
||||||
|
When storing and retrieving, the key provided to the RPCs is not required to be only the user's ID. It can include a list of strings which act as a path into the data stored at the user's key, targetting it specifically for update or retrieval. This lets the network minimize data transfer, because only the relevant information has to move around.
|
||||||
|
|
||||||
|
The specific content of the user's keys is determined partially by the protocol and partially by the client software. Early versions of the protocol use a DHT schema version that defines a fairly simple social network oriented schema. Later versions will enable a more generic schema so that client plugins can store and display richer information.
|
||||||
|
|
||||||
|
The stateful nature of the key-value store means that values will change over time, and actions may need to be taken in response to those changes. A polling mechanism could be used to periodically check for new values, but this will lead to lots of unnecessary traffic in the network, so to avoid this, Veilid allows peers to send `watch_value` RPCs, with a DHT key (with subkeys) as its argument. The receiver would then store a record that the sender of the RPC wants to be alerted when the receiver gets subsequent `set_value` calls, at which time the receiver sends the sending peer a `value_changed` RPC to push the new value. As with other RPC calls, `watch_value` needs to be periodically re-sent to refresh the subscription to the value. Additionally, also as with other calls, `watch_value` may not succeed on the receiver, which instead might return other peers closer to the value, or might return other peers that have successfully subscribed to the value and thus might act as a source for it.
|
||||||
|
|
||||||
|
TODO How to avoid replay updates?? maybe via a sequence number in the signed patch?
|
||||||
|
|
||||||
|
## Appendix 1: Dial Info and Signaling
|
||||||
|
|
||||||
|
## Appendix 2: RPC Listing
|
7
doc/security/poc/large-websocket-key-v0.2.2.py
Normal file
7
doc/security/poc/large-websocket-key-v0.2.2.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# When pointed at veilid-server 0.2.2 or earlier, this will cause 100% CPU utilization
|
||||||
|
|
||||||
|
import socket
|
||||||
|
s = socket.socket()
|
||||||
|
s.connect(('127.0.0.1',5150))
|
||||||
|
s.send(f"GET /ws HTTP/1.1\r\nSec-WebSocket-Version: 13\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: {'A'*2000000}\r\n\r\n".encode())
|
||||||
|
s.close()
|
Loading…
Reference in New Issue
Block a user