/!\ Significant : RFC WS2P V2 /!\

ws2p
duniter-rs
duniter-ts
ws

#21

Ça fait bien 2 semaines que j’ai un rappel de lecture, mais je viens enfin de trouver un moment tranquille pour lire et répondre. Voici donc mon retour.

Tu te bloques tout seul alors, car tu n’as pas besoin de recoder BMA pour te synchroniser : il te suffit d’utiliser l’API BMA des nœuds qui la proposent : exemple https://g1.cgeek.fr/blockchain/blocks/10/0 te renverra 10 blocs à partir du bloc#0.

Enfin bon tu fais comme tu veux, au moins en te créant ce blocage ça te force à sortir WS2Pv2, ce qui n’est pas plus mal :slight_smile:

Mais alors à part les fiches de pair v11, que va générer comme documents l’implémentation Rust ? Et si tu ne prévois pas de générer de document v10, ça veut dire que tant que le protocole Duniter ne définit pas le format binaire, un nœud Rust ne fera que miroir ?

Je n’aurais pas mis les champs IP4, IP6, HOST, PORT et PATH dans la définition générale, ceux-là sont plutôt dépendants de chaque API, non ?

Je dirais que cette partie est un détail d’implémentation, une recommandation, mais n’est pas nécessaire à l’établissement d’une connexion entre 2 pairs.


Sur le reste de la RFC, c’est très technique. J’ai du mal à me mettre dedans en ce moment, mais au pire ma vérification n’est pas primordiale car même tout seul en l’implémentant tu pourras te rendre compte des erreurs s’il y en a.


#22

J’ai envie de faire les choses proprement, et en l’occurrence je n’ai pas envie d’intégrer dans Duniter-Rust une dépendance a une API obsolète. En effet dans l’absolu je pourrais, mais j’estime que ce serait une perte de temps, je préfère consacrer ce temps au développement de ws2p v2 :slight_smile:

Les heads v3 également. Ainsi que les Blocks en JSON (uniquement sur une connexion avec un noeud duniter-ts, duniter-rust sait dynamiquement a quel implémentation il est connecté).
les connexions ws2p entre nœuds Rust s’échangeront tout les types de documents mais uniquement en binaire.

Non les noeud Rust pourront être membre calculant, en revanche, les documents qu’ils reçoivent via leur api client ne pourront pas être transmis aux nœuds duniter-ts, sauf a ce que tu implémente la feature ABF a duniter-ts. (Pour ça il faudra que je publie la spécification des formats binaires des documents, ce que je ferai si tu me le demande :slight_smile: ).
Mais en soi ce n’est pas gênant car d’ici que les nœuds duniter-rust est une api client pleinement opérationnelle et effectivement utilisée par les clients on sera déjà dans la binarisation du protocole.

Le format générique permet de spécifier s’il y a ou non des IP et s’il y a ou non un domaine. Il permet également d’intégrer des features réseau pouvant être interprété comme des protocoles spécifiques.
En tout les cas, et quel que soit le protocole réseau, tout endpoint doit etre joignable via un identifiant unique sur le réseau (ip ou domaine). Dojnc c’est un invariant qui ne me semble pas déconnant.

Si l’on souhaite un réseau solide et efficace, je pense que les règles générale de priorisation doivent être les mêmes sur les deux implémentations, ça me semble important. aprèsil y aura des spécificités, mais ce que j’écris dans la rfc c’est un socle de base (d’ailleurs déjà intégré a Duniter-ts, c’était l’une de mes contributions a ws2pv1).

Ben c’est surtout une description bête et méchante du format binaire des messages. Après effectivement c’est en codant qu’on remarquera les erreurs de conception :slight_smile:


#23

Bon je vais laisser d’autres personnes répondre, je ne crois pas être très utile ici.


#24

Bon je suis déçu personne n’a trouvé l’erreur, et je doit commencer l’implémentation donc c’est trop tard :stuck_out_tongue:

Le correctif est là : https://git.duniter.org/nodes/common/doc/commit/ad77d759715f222c2da96ccd3ad6edfba0a36c83

Explications : Le format générique des messages ws2p v2 indique bien que la charge utile peut etre un tableau d’éléments, et seule la taille totale de la charge utile est connue. Chaque élément doit donc fournir des champs permettant d’indiquer sa taille, sauf pour les type d’éléments qui ne peuvent être présent qu’une fois par message.
Ainsi, dans un tableau de HEADv3, chaque HEAD à une taille inconnue, il n’est donc pas possible de calculer la taille du champ soft-version, il faut nécessairement l’indiquée :wink:


#25

Le point sur l’avancement de WS2Pv2 :

Sur ce je part en vacances puis je déménage, donc le dev de durs va être en pause tout le mois d’août.
Reprise en septembre, bonnes vacances a tous :blush:


#26

Quitte a faire un truc bien propre et évolutif pour WS2Pv2 j’ai envie d’y intégrer de base des formats capables de stocker des clés publiques et signatures de n’importe quel algorithme.
Ceci dans l’objectif de ne pas avoir besoin de créer un WS2pv3 le jours ou l’on intégrera les ring signatures : Ring signatures pour anonymiser les noeuds sur le réseau

J’ai mis à jours la RFC en ce sens, je vous invite donc a la reconsulter :slight_smile:

Dans les grandes lignes, voici le format d’une publique :

data name size in bytes data type
pubkey_size 2 u16
algorithm_id 1 algorithm
pubkey_content pubkey_size [u8; pubkey_size]

A noter qu’il est nécessaire d’indiquer la taille de la clé publique car dans certains cas connaître l’algo ne suffira pas a connaître la taille attendue pour la clé publique. C’est le cas notamment pour les ring signatures.

Et le format du type algorithm :

algorithm code
null* 0x00
Ed25519 0x01
Schnorr 0x02

Pensez-vous qu’un seul octet soit suffisant ? Cela nous laisse quand même la possibilité de supporter 255 algo différents ! Ou faut t’il que je prenne 2 octets pour être sur ?

Enfin le format de la Signature :

data name size in bytes data type
sig_size 2 u16
sig_content sig_size [u8; sig_size]

A noté qu’il n’est pas nécessaire de spécifier l’algo attendu ici, car lorsque l’on vérifie une signature on connaît nécessairement la clé publique (ou le groupe de clé publique) associé et donc on sait quel algo est attendu.

Autre question : De la même façon on pourrait intégrer la capacité a stocker des hashs de différents algos, mais comme il n’est pas prévu d’abandonner sha256 a moyen terme je pense que c’est prématuré, qu’en dites vous ?


#27

La possibilité de supporter plusieurs algos pour les clefs est une bonne idée, mais si j’ai bien compris ce n’est que pour la partie échange réseau de message de peering ?

255 me paraît largement suffisant pour que cela fonctionne jusqu’à ta retraite. :wink:

Pour les hashs, je ne connaît pas assez le protocole pour dire s’il en faut plusieurs. Je dirais que si on peut choisir parmi plusieurs, on utilise ensuite toujours le même pour éviter trop de complexité.

My two cents.


#28

pubkey_size ne pourrait pas justement faire parti du pubkey_content dans le cas où la taille est variable, évitant ainsi de stocker la taille des clés pour les algos à taille fixe ? (et ayant donc la même taille que proposé quand c’est le cas)

A quoi correspond l’algorithme null ?


#29

En effet c’est une optimisation possible, après ça complexifie encore la lecture du format juste pour gagner 2 octets, mais ca me va je ferai ma modif ce soir :slight_smile:

Cela indiquera qu’il n’y a pas de clé publique ou que l’algo correspondant est inconnu. Je pense qu’il y aura des cas ou ça sera utile, donc je préfère le prévoir. Et en Rust nous n’aurons pas d’effet de bord car l’algo sera stocké dans un Option<NonZeroU8> :wink:

https://doc.rust-lang.org/stable/core/num/struct.NonZeroU8.html


#30

Pas vraiment, vu que ça permet à chaque algo de définir son propre format de payload, ce qui pourra être utile pour des algos qui auront besoin d’autre chose que la taille :slight_smile:

On a donc un format type + payload, le format de payload dépendant du type et pouvant être de la forme key ou size + key, etc.

clap clap


#31

Finalement je suis parti sur un conteneur générique (une “Box”) pouvant contenir n’importe quoi dans le champ content. Le format de ce champ dépendra de l’algorithme et de ces fonctionnalités (ce qui veut dire que les algorithme avancés devront intégrer leur propre système de versioning).
Dans le cas de l’algo Ed25519 (le seul actuellement implémenté), le champ content est simplement un tableau d’octets contenant directement la pubkey/signature, mais dans le cas des ring signatures et autre joyeuseté le champ content pourra être un type très complexe :slight_smile:

Voici donc les spécifications de ces “Box” :

PubkeyBox

Contains the signatory’s public key.

data size in bytes data type
size 2 u16
algo 1 algorithm
content size - 1 ?*

size := Size of the whole PubkeyBox (size field excluded).

*The type of field content depends on the algorithm. In the case of Ed25519, content is a 32-byte array containing the public key.

SigBox

Contains the cryptographic signature of a document.

data name size in bytes data type
size 2 u16
content size ?*

size := Size of the whole SigBox (size field excluded).

*The type of field content depends on the algorithm. In the case of Ed25519, content is a 64-byte array containing the signature.

Cela signifie également que l’implémentation d’un nouvel algo de clés devra nécessairement s’accompagner de spécifications qui décrivent le format du champ content.


#32

Je vois que tu utilises un codage de la forme :

Taille Clef Valeur
Taille de clef+valeur code clef contenu

Hors j’ai travaillé dans mon métier sur le format vidéo professionnel MXF qui utilise un codage approchant assez efficace et normalisé, le codage KLV (Key Length Value). ce codage peut être “nested”, puisque la Valeur peut contenir elle-même des KLV pour coder des structures complexes.

Clef Taille Valeur
code clef taille de valeur contenu

Je pense que cela peut donc t’intéresser.

Les spécifications ITU en français : http://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1563-1-201103-I!!PDF-F.pdf

A noter que pour de grandes tailles, le champ Taille utilise un codage appelé BER. Je te laisse le découvrir.

[EDIT] La page Wikipédia en anglais.


Binarisation vs Format text du protocole blockchain
#33

Merci @vtexier ça a l’air très intéressant, je vais essayer de me motiver a réécrire toute la RFC avec le codage KLV :slight_smile:


#34

Même réponse que pour les extensions : 1 seul octet, donc un des cas est reserver pour dire : L’algo n’est pas dans le 1er octet, il y en a donc un 2nd à cette fin, et du coup, tu peux probablement réduire à 15 au lieu de 255 les possibilité, vu que ça deviens extensible au besoin.


#35

Attention, il y en a à qui ça à joué des tours (ssl je crois dont une faille était basé sur le fait qu’une algo null existait et du coup permettait de forcer des échange en clair tout en étant considéré comme sécurisé).


#36

Bon j’ai pris le temps de regarder plus en détail KLV et ça ne me semble finalement pas souhaitable de l’utiliser pour WS2Pv2, de toute façon la RFC se base déjà sur des principes assez similaires.
Mais avoir une clé pour chaque objet n’est pas utile car on n’a qu’un seul et unique objet Message qui indique quel type de payload il contient donc on sait toujours quel type de donnée est attendue.


#37

Ok, j’y est réfléchi et finalement je pense qu’on pourra se passer de null, je l’ai donc supprimé des spec :slight_smile:


#38

Je suis en train d’implémenter les messages de négociation de connexion (CONNECT, ACK, etc) et j’ai décidé de modifier le message ACK, voici pourquoi :

L’objectif du message ACK est de transmettre une signature du challenge que nous a proposé l’autre noeud pour lui prouver que l’on possède bien la clé privée correspondante a la clé publique sous laquelle on se présente.
Cela étant, dans WS2Pv2 tout les messages sont inclus dans un même conteneur qui est nécessairement signé, donc plutôt que de transmettre 2 signatures (celle du challenge a signer + celle du conteneur) autant ne transmettre qu’une seule signature (celle du conteneur) et recopier le challenge a signer dans la payload du message ACK afin que le dit challenge soit bien signé.
C’est exactement le changement que je viens de pusher sur la RFC : https://git.duniter.org/nodes/common/doc/commit/c8c5f5b2554eea374422d82557eeae330c7e068b

S’il y a des contributeurs qui prévoient d’implémenter WS2Pv2 dans leur programmes, je vous invite a attendre que j’ai terminée l’implémentation dans Durs afin que la RFC se stabilise, car forcément en faisant on se rend compte qu’il y a des réajustements a faire.

Aussi pour Duniter (Typescript) je prévois de faire du binding via Neon pour pouvoir réutiliser le code Rust qui converti les messages WS2Pv2 entre les formats binaire et “objet en mémoire” (Neon permettant a Rust de transmettre directement un JsObject a la VM Node). Ainsi la seule chose a recoder en Typescript sera la logiques des contrôleurs et des services (comment réagir a tel message selon le contexte).


#39

Très bien tout ça, good job :ok_hand:


#40

Mes formats binaires “fait maison” m’obligent a coder moi même tout le code de Sérialisation et de Dé-sérialisation, ce qui outre d’être très long et fastidieux, peut être source de nombreux bug qui pourraient être évités.

J’ai donc décidé de refondre le format de tout les messages de la RFC dans un standard compréhensible par une librairie de serialisation (qui serait configurée pour) afin de ne pas avoir à écrire moi-même le code de sérialisation/désérialisation.

Outre l’énorme gain en temps et en fiabilité (un code qu’on n’écris pas est une source de bug en moins), cela me permettrait de créer beaucoup plus facilement un convertisseur binaire<->texte exportable en librairie dynamique utilisable dans la plupart des langages (en tout cas le binding Rust est possible dans NodeJs, Python et Java).

Je me suis donc plongé ces derniers jours dans les spécifications de bincode, et j’ai également fait de nombreux test de sérialisation avec bincode pour être sûr de bien comprendre comment il sérialize afin de pouvoir spécifier les formats correctement dans la RFC.

Je suis donc en train de réécrire tout les formats de la RFC, je pusherai ça d’ici la fin de semaine. La logique de WS2Pv2 ne change pas, il s’agit juste d’un changement du format binaire des messages :slight_smile: