# Construire un messager décentralisé : Intégrer libp2p dans Flutter via Go
Les applications de messagerie d'aujourd'hui exigent confidentialité et décentralisation. Cet article détaille l'implémentation d'un messager P2P avec chiffrement de bout en bout basé sur libp2p. La solution utilise Go pour la logique réseau, Flutter pour l'interface utilisateur, et FFI pour l'interaction entre composants. L'architecture élimine les serveurs centraux, garantissant la sécurité même en contournant NAT et pare-feu.
Bases de l'architecture : Flutter, Go et FFI
Le défi principal consiste à intégrer libp2p dans une application mobile sans compromettre les performances. Flutter gère l'interface, tandis que la logique réseau est déplacée vers un binaire Go compilé en :
.dylibpour macOS.sopour Android- Bibliothèque statique
.apour iOS
Dart et Go interagissent via FFI (Foreign Function Interface) en utilisant des fonctions compatibles C. Cela évite les surcoûts de sérialisation et maintient les vitesses d'échange de données natives. La gestion de la mémoire est cruciale : les données de chaînes passées de Go vers Dart nécessitent une libération manuelle via FreeString.
Compilation de la bibliothèque Go pour les plateformes mobiles
Le processus de compilation nécessite des paramètres spécifiques à chaque plateforme. Pour Android, utilisez NDK :
CGO_ENABLED=1 GOOS=android GOARCH=arm64 \
CC=$ANDROID_NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang \
go build -buildmode=c-shared \
-o libp2p_network.so \
./main.go
Pour iOS :
CGO_ENABLED=1 GOOS=ios GOARCH=arm64 \
CC=$(xcrun --sdk iphoneos --find clang) \
CGO_CFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path) -arch arm64 -miphoneos-version-min=12.0" \
CGO_LDFLAGS="-isysroot $(xcrun --sdk iphoneos --show-sdk-path) -arch arm64 -miphoneos-version-min=12.0" \
go build -buildmode=c-archive \
-o libp2p_network.a \
./main.go
Sur Android, la bibliothèque est placée dans jniLibs/arm64-v8a/. Sur iOS, elle est liée via Xcode. Pour le simulateur, compilez pour x86_64 et fusionnez les archives avec lipo -create.
Pont FFI : Interaction entre Go et Dart
L'exportation de fonctions depuis Go nécessite des règles strictes :
- Toutes les fonctions sont marquées avec
//export - Inclure une fonction
mainvidefunc main() {} - Gestion de la mémoire :
C.CString()alloue dans le tas C, que Dart doit libérer viaFreeString
Exemple d'export Go :
//export StartNode
func StartNode(storagePath *C.char) *C.char {
path := C.GoString(storagePath)
node, err := p2p.NewNode(path)
// ...
return C.CString(node.GetPeerID())
}
En Dart, chargez la bibliothèque et liez les fonctions :
_loadLibrary() {
if (Platform.isAndroid) {
_lib = DynamicLibrary.open('libp2p_network.so');
}
_startNode = _lib!.lookupFunction<
Pointer<Utf8> Function(Pointer<Utf8>),
Pointer<Utf8> Function(Pointer<Utf8>)
>('StartNode');
}
L'appel de la fonction implique conversion de chaînes et gestion manuelle de la mémoire :
final pathPtr = dir.path.toNativeUtf8();
final resultPtr = _startNode(pathPtr);
final peerId = resultPtr.toDartString();
_freeString(resultPtr);
calloc.free(pathPtr);
Utilisation de libp2p : Création d'un nœud et échange de messages
L'initialisation du nœud configure les transports et protocoles :
func NewNode(storagePath string) (*Node, error) {
h, err := libp2p.New(
libp2p.Identity(priv),
libp2p.ListenAddrStrings(
"/ip4/0.0.0.0/tcp/0",
"/ip4/0.0.0.0/udp/0/quic-v1",
),
libp2p.EnableNATService(),
libp2p.EnableRelay(),
libp2p.NATPortMap(),
)
// ...
}
Chaque appareil obtient un PeerID unique — un hachage de la clé publique Ed25519. L'envoi de messages utilise un échange basé sur des flux :
func (n *Node) SendMessage(peerIDStr, content, msgType, id string) error {
peerID, _ := peer.Decode(peerIDStr)
s, err := n.host.NewStream(ctx, peerID, "/messaging/1.0.0")
s.Write(data)
s.Close()
return nil
}
Les messages sont livrés directement en ligne ou via un nœud relais hors ligne. Les données chiffrées sont stockées temporairement jusqu'à ce que le destinataire se connecte.
Assurer la connectivité dans des conditions réseau difficiles
Pour les appels vocaux, un système de transport à trois niveaux est implémenté :
- Transport UDP direct (Pion ICE) — canal principal avec latence minimale. Les candidats ICE sont échangés via des messages libp2p.
- DCUtR (Hole Punching) — traversée NAT utilisant Relay.
- Relais de secours — relais de trafic via un nœud serveur en cas d'échec de connexion directe.
Le code de gestion du flux audio montre un format de paquet personnalisé :
func (n *Node) SendAudio(data []byte) error {
packet := make([]byte, 1+2+len(data))
packet[0] = 0xFE
binary.BigEndian.PutUint16(packet[1:], uint16(len(data)))
copy(packet[3:], data)
call.Stream.Write(packet)
return nil
}
La réception de données utilise une boucle pour détecter les octets de synchronisation et traiter les trames Opus.
Chiffrement et sécurité : Double Ratchet en action
Pour les chats privés, le protocole Double Ratchet fournit une secret parfait de transmission en avant et une sécurité post-compromission. Fonctionnalités clés :
- Chaque message utilise une clé unique
- Rotation périodique des clés
- Protection contre la compromission à long terme des clés
Le système assure un chiffrement de bout en bout sans que les clés passent par des nœuds intermédiaires. Toutes les opérations crypto s'exécutent sur l'appareil de l'utilisateur, évitant les fuites même sur les nœuds relais.
Gestion des scénarios hors ligne sans compromettre la confidentialité
Les notifications push servent uniquement de déclencheurs de réveil de l'application. Important :
- Les messages push ne contiennent aucune donnée de message
- Aucune info sur l'expéditeur
- Le déclencheur ne contient qu'un drapeau de service
Flux de travail :
- Notification push silencieuse arrive
- Le système d'exploitation active brièvement l'application
- Le nœud Go se connecte et récupère les paquets chiffrés
- Le déchiffrement se fait localement
- Une notification locale est générée pour l'utilisateur
Cela préserve le E2EE même avec des services push centralisés.
Points clés
- Le pont FFI exige une gestion stricte de la mémoire : des fuites surviennent si
FreeStringest omis - Circuit Relay v2 est crucial pour la traversée NAT mais ajoute 15-20 % de latence
- Double Ratchet fournit PFS mais nécessite une synchronisation d'état entre appareils
- Les déclencheurs push ne compromettent pas la confidentialité car ils ne transportent aucune charge utile
- Les builds iOS nécessitent une fusion manuelle d'archives pour simulateur et appareil
— Editorial Team
Aucun commentaire pour le moment.