Comment construire une application en utilisant le mixnet: l'exemple de Pastenym

·

10 min read

Décharge de responsabilité: dans cet article, nous faisons l'hypothèse que la lectrice ou le lecteur possède des connaissances techniques puisque cet article est destiné à des développeuses et développeurs. Nous avons donc fait donc le choix de ne pas détailler chaque terme.

Le mixnet de Nym offre de très intéressantes garanties en ce qui concerne la vie privée, comme nous l'avons déjà précédemment énoncé dans nos articles précédents. Dans cet article, nous expliquons comment développer votre application pour utiliser le mixnet proposé par Nym Technologies, afin de bénéficier des garanties que ce dernier peut apporter en terme de vie privée. Pour illustrer nos propos, nous prendrons comme exemple notre service de copier-coller anonyme en ligne: Pastenym (en version v2.0.2). N'hésitez pas à consulter le code source sur le dépot de l'application!

Au moment de l'écriture de cet article, notre instance de Pastenym (https://pastenym.ch) est indisponible, à cause d'un problème avec un composant proposé proposé par Nym. Dans l'attente d'une solution, nous vous recommandons d'utiliser une autre instance, telle que https://pastenym.mynixnode.com

Si vous ne savez pas ce qu'est Pastenym, assurez-vous de lire cet article de notre blog! Si vous n'êtes pas familière ou familier avec le mixnet de Nym, nous vous recommandons cette introduction aux mixnets, qui est une traduction de la version originale du blog de Nym.

Naturellement, si votre application est faite de plusieurs composants (application client-serveur) ou implique différents acteurs (application pair-à-pair), ceux-ci auront probablement besoin de communiquer entre eux. Dans un premier temps, nous allons revoir la manière traditionnelle d'échanger des données entre applications, puis nous présenterons comment le faire en utilisant le mixnet.

Méthode traditionnelle

La manière traditionnelle pour échanger des informations entre composants ou applications est via une API REST, GraphQL ou personnalisé, ou alors en utilisant un protocole personnalisé, à travers des (web)sockets. Les messages échangés sont envoyés via des requêtes HTTP, à travers les internets, d'un composant à un autre, chacun identifié par une adresse IP et un port.

Si nous avions voulu que les composants de Pastenym communique de manière traditionnelle, notre frontend en React aurait fait des requêtes sur des points d'entrées de l'API REST de notre backend en Python, lequel aurait probablement été développé en utilisant un cadriciel orienté API, tel que Flask ou FastAPI.

Comment envoyer et recevoir des messages à travers le mixnet?

Lorsque votre application communique avec le mixnet, ce n'est pas l'application elle-même qui parle avec le mixnet. Pour échanger des messages sur le mixnet, votre composant à besoin d'un client nym. Le client nym est responsable du chiffrement et déchiffrement des messages, de communiquer avec les autres composants du mixnet, de gérer les Blocs de Réponses à Usage Unique (BRUU)s (SURBs en anglais) et de plein d'autres choses que vous ne voudriez probablement pas à avoir à gérer vous-même dans votre application. Pour en savoir plus sur les clients nym, référez-vous à la documentation officielle (en anglais).

Les clients nym sont identifiés par des clientIDs. Un clientIDest une chaîne de caractères, composée de trois parties, délimitées par deux points '.'. Vous pourrez trouver plus d'information sur le système d'adressage ici (en anglais).

Les clients nym peuvent s'envoyer des messages entre eux, à travers le mixnet. Ils peuvent s'échanger des données brutes ou du texte, en fonction de vos besoins et de votre configuration. Dans Pastenym, les composants s'échangent des charges utiles au format JSON, qui contiennent tout ce dont nous avons besoin pour enregistrer et récupérer des pastes.

Nous allons maintenant détailler l'utilisation des deux clients nym que nous utilisons dans Pastenym: le client websocket et le client en Web Assembly.

Le client websocket

Ce client nym est pratique car il s'exécute sur toutes les plateformes et peut être utilisé indépendamment du langage que vous avez choisi pour votre application. Cependant, il nécessite de faire fonctionner un exécutable indépendant, avec lequel votre application va communiquer à travers une websocket. Pour ces raisons, ce client nym est le choix par défaut pour les applications backend. Vous pouvez lire ici la documentation de Nym sur ce client (en anglais).

Voici une marche à suivre, étape par étape, pour utiliser le client websocket:

  1. Télécharger le client nym websocket depuis les releases du dépôt Github de Nym ou compilez le vous-mêmes (les instructions, en anglais, se trouvent ici).

  2. Assurez-vous que le client nym soit exécutable. Il faudra probablement adapter la commande suivante si vous souhaitez qu'un autre utilisateur puisse l'exécuter le client nym

     chmod u+x nym-client
    
  3. Initialiez le client nym websocket comme suit:

     ./nym-client init --id <nom_de_votre_choix>
    

    où vous devez remplacer <nom_de_votre_choix> par un nom de votre choix. Vous devrez vous souvenir de ce nom quand vous voudrez lancer le client nym. Le nom est associé avec le clientID: si vous utilisez un nom différent, le clientID sera différent.

    Si vous souhaitez fournir un service à travers le réseau de Nym (ce qui est notre cas avec le backend de Pastenym), vous ne souhaiterez probablement pas que le clientID de votre application change.

  4. Lancer le client nym websocket comme suit:

     ./nym-client run --id <nom_de_votre_choix>
    

    Vous ne pouvez pas lancer le client nym pour le <nom_de_votre_choix> sans l'avoir initialisé au préalable.

    Si cette commande s'est correctement exécutée, vous devriez voir dans les logs, une ligne affichant le clientID de ce client nym, semblable à:

     > The address of this client is: GCdwdkpnn94YjsDqkQjXQi7mMkYxrf87LDCkdi8Wsh4f.E4edYXjPN4DZi77q4n6JEUavVwdFhFFj2J2yEGpW15Tq@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN
    

    Vous pourriez également avoir constaté la ligne suivante dans les logs:

     > Running websocket on "127.0.0.1:1977"
    

    Indiquant l'adresse du websocket à laquelle votre application devra se connecter pour échanger avec le mixnet. Vous constaterez probablement que cette websocket n'est disponible que sur l'hôte local, donc votre application devra être sur la même machine que le client nym. Si vous souhaitez exécuter votre logiciel sur une machine différente (ou dans un container docker), vous pouvez utiliser notre image docker pour le client nym (disponible ici).

  5. Dans votre application, vous pouvez maintenant vous connecter à la websocket indiquées dans les logs en utilisant une librairie de websocket disponible dans le langage de programmation de votre application, comme nous l'avons fait pour Pastenym, en Python. Ci-après, vous trouverez un extrait de notre application, donc vous pouvez trouver le code complet ici:

     import websocket
     import json
     import rel
     ​
     class Serve:
         def __init__(self):
             websocket.enableTrace(False)
             self.ws = websocket.WebSocketApp(f"ws://127.0.0.1:1977",
                 on_message=lambda ws, msg: self.on_message(ws, msg),
                 on_error=lambda ws, msg: self.on_error(ws, msg),
                 on_close=lambda ws: self.on_close(ws),
                 on_open=lambda ws: self.on_open(ws),
                 on_pong=lambda ws, msg: self.on_pong(ws, msg)
             )
             self.ws.run_forever(dispatcher=rel, ping_interval=30, ping_timeout=10)
     ​
             rel.signal(2, rel.abort)  # Keyboard Interrupt
             rel.dispatch()
             self.ws.close()
    
         def on_message(self, ws, message):
             # Implémentez votre logique applicative
             try:
                 received_message = json.loads(message)
             except JSONDecodeError as e:
                 print(f"Failed to decode JSON message: {e}")
                 return
             print(f"Got: {received_message}")
    
         # on_error, on_close, on_open, on_pong functions
    
         def send_reply_to(self, recipient, message):
             if recipient is None or self.ws is None:
                 print("Cannot send message: recipient or ws is None")
                 return
    
             self.ws.send(json.dumps({
                 "type": "reply",
                 "message": message,
                 "senderTag": recipient,
             })
    

    Si vous utilisez du Golang, nous avons développé un module pour faciliter l'intégration du mixnet dans votre application, vous pouvez lire l'article le présentant et le consulter dans notre dépôt: github.com/notrustverify/nymsocketmanager.

Le client en Web Assembly

Nous expliquons ici comment utiliser le client en web assembly pour une application web en JS/TS, cependant un nouveau SDK en Typescript est sorti début octobre 2023, que nous intégrerons prochainement dans Pastenym. Nous pourrions également sortir un guide pour celui-ci ou mettre à jour cet article. Gardez-donc un œil sur notre blog ;)

Le client en web assembly est un tout petit peu plus compliqué à utiliser et il est possible que vous deviez vous référer un peu plus en détail à notre code ou aux exemples de Nym. Dans notre cas, nous l'avons utilisé dans le frontend de Pastenym, fait en React. Ce frontend est paqueté avec Webpack. Vous pouvez trouver la documentation pour le client en web assembly ici (en anglais).

Tout d'abord, il faut installer le SDK Nym, qui facilite l'utilisation du client Wasm, en utilisant la commande suivante, depuis le dossier de votre projet:

npm install @nymproject/sdk
# Vous pouvez également utiliser yarn ou d'autres gestionnaires de paquets

Dans notre frontend React, nous avons regroupé le code en charge de la création du client Nym dans un fichier (celui-ci), où nous faisons les choses suivantes:

import { createNymMixnetClient } from '@nymproject/sdk'

export async function connectMixnet() {
    const nym = await createNymMixnetClient()

    const nymApiUrl = 'https://validator.nymtech.net/api'
    const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM'

    // WSS est obligatoire pour un site en HTTPS
    const gatewayListener = 'wss://gateway1.nymtech.net:443'

    // démarre le client et le connecte à une gateway
    await nym.client.start({
        clientId: '<another_name_of_your_choosing>',
        nymApiUrl,
        preferredGatewayIdentityKey: preferredGatewayIdentityKey,
        gatewayListener: gatewayListener,
    })

    return nym
}

Nous pouvons voir que nous utilisons la librairie de Nym pour créer un client nym et que nous démarrons avec des paramètres spécifiques, entre autres, un clientID, que nous vous invitons à changer, comme pour le client websocket.

L'objet nym retourné par la librairie Nym, après être démarré, permet d'abonner des fonctions avec les fonctions nym.events.subscribeToConnected, nym.events.subscribeToTextMessageReceivedEvent ou nym.events.subscribeToRawMessageReceivedEvent pour recevoir des messages, ainsi que d'envoyer des messages vers le mixnet en utilisant nym.client.Sendou nym.client.rawSend.

La portion de code au-dessus nous permet d'appeler la fonction connectMixnet() dans nos composants. Par exemple, dans le composant UserText.js (la page d'accueil de notre application), nous avons le code suivant qui, lors que le composant sera monté, chargera le client nym et renseignera les fonctions de callback à appeler lors de la réception de messages depuis le mixnet (ici). Dans notre cas, il s'agit de la fonction displayReceiveddu composant UserInput qui sera en charge de traiter les messages reçus du mixnet.

import * as React from 'react'
import { connectMixnet } from './context/createConnection'

class UserInput extends React.Component {

    // Beaucoup d'autres choses ici

    async componentDidMount() {
        this.nym = await connectMixnet()

        // Lorsque connecté, nous recevrons un message avec notre clientID
        this.nym.events.subscribeToConnected((e) => {
            if (e.args.address) {
                // Nous stockons notre clientID dans l'état du composant
                this.setState({ self_address: e.args.address })
            }
        })

        this.nym.events.subscribeToRawMessageReceivedEvent((e) => {
            this.displayReceived(this.decoder.decode(e.args.payload))
        })
    }

    async sendMessageTo(payload) {
        if (!this.nym) {
            console.error('Could not send message because worker does not exist')
            return
        }

        await this.nym.client.rawSend({
            payload: this.encoder.encode(payload),
            recipient: recipient
        })
    }
}

Et c'est ainsi que nous utilisons le SDK Nym pour avoir un frontend communiquant avec le mixnet.

Conclusion

Et voilà! Vous savez maintenant comment envoyer et recevoir des messages à travers le mixnet depuis votre application! Vous n'allez peut-être utiliser qu'un seul de ces deux clients nym, peut-être les deux ou même encore un autre, cela dépendra de votre application. Nous ne sommes pas entré en détail des SURBs, du dernier client nym mentionné dans la documentation officielle ou du tout dernier SDK Typescript pour garder les choses simples. Cependant, vous pouvez toujours consulter la documentation officielle, les exemples de Nym ou notre code pour en apprendre plus sur ces éléments!

Quelques remarques concernant la vie privée

Ce n'est pas parce qu'une application utilise le mixnet que les garanties en terme de respect de la vie privée, fournies par le mixnet, s'applique à l'application, ni que l'application sera parfaitement respectueuse de la vie privée! Pour le dire autrement, l'utilisation du mixnet est une condition nécessaire mais non suffisante pour respecter la vie privée des utilisatrices et utilisateurs.

Si votre application nécessite que les utilisatrices et utilisateurs fournissent un identifiant unique (pseudo, email, numéro de téléphone,...) pour se connecter à un compte, par exemple, alors l'émetteuse ou l'émetteur d'un message sera toujours identifiable, malgré l'utilisation du mixnet. La propriété de "sender pseudonimity" sera perdue, étant donné que l'émetteuse ou l'émetteur du message sera identifiable.

De manière similaire, si votre application nécessite que l'utilisatrice ou l'utilisateur visite une URL ou un point d'entrée spécifique pour utiliser une certaine fonctionnalité de votre application (/login, /sendMessage, /profile?id=1234, ...), chaque utilisation de cette fonctionnalité engendra une requête HTTP, qui ne transitera pas à travers le mixnet et révèlera des informations sur l'utilisatrice ou l'utilisateur ainsi que leur activité auprès de leur FAI et des autres intermédiaires de l'internet.

Pour ces raisons, votre application devrait être conçue selon le principe de respect par défaut de la vie privée et chaque choix d'implémentation devrait être soigneusement réfléchi pour ne pas ébrécher la vie privée de vos utilisatrices et utilisateurs.