Zpět na domů

Integrace Dart-Go FFI pro libp2p ve Flutteru — technická příručka

Článek popisuje implementaci decentralizovaného messengeru s end-to-end šifrováním prostřednictvím integrace libp2p (Go) do aplikace Flutter. Podrobně jsou prozkoumány interakce FFI, obcházení NAT, bezpečnost Double Ratchet a zpracování offline scénářů. Řešení vylučuje centrální servery a zachovává soukromí dat.

Kompletní příručka k vytvoření decentralizovaného messengeru na Flutteru a Go
Advertisement 728x90

# Budování decentralizovaného messengeru: integrace libp2p do Flutteru přes Go

Současné messengery vyžadují soukromí a decentralizaci. V tomto článku rozebereme implementaci P2P messengeru s end-to-end šifrováním na bázi libp2p. Řešení využívá Go pro síťovou logiku, Flutter pro UI a FFI pro interakci mezi komponenty. Architektura vylučuje centrální servery a zajišťuje bezpečnost i při obcházení NAT a firewallů.

Základy architektury: Flutter, Go a FFI

Klíčovým úkolem je integrace libp2p do mobilní aplikace bez kompromisů v výkonu. Flutter se stará o uživatelské rozhraní, zatímco síťová logika je vynechána do Go binárníku, který se kompiluje do:

  • .dylib pro macOS
  • .so pro Android
  • Statické knihovny .a pro iOS

Komunikace mezi Dart a Go probíhá přes FFI (Foreign Function Interface) s využitím C-kompatibilních funkcí. To umožňuje vyhnout se nákladům na serializaci a zachovat nativní rychlost výměny dat. Je důležité zohlednit specifika správy paměti: řetězcová data přenášená z Go do Dart vyžaduje ruční uvolnění přes FreeString.

Google AdInline article slot

Sestavení Go knihovny pro mobilní platformy

Proces kompilace vyžaduje platformově specifická nastavení. Pro Android se používá 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

Pro 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

Na Androidu se knihovna umístí do jniLibs/arm64-v8a/, na iOS se linkuje přes Xcode. Pro simulátor je potřeba sestavení pro x86_64 a sloučení archivů přes lipo -create.

Google AdInline article slot

FFI most: komunikace mezi Go a Dart

Export funkcí z Go vyžaduje dodržení přísných pravidel:

  • Všechny funkce jsou označeny komentářem //export
  • Nutná je prázdná funkce func main() {}
  • Správa paměti: C.CString() alokuje paměť v C heapu, kterou musí Dart uvolnit přes FreeString

Příklad exportu z Go:

//export StartNode
func StartNode(storagePath *C.char) *C.char {
    path := C.GoString(storagePath)
    node, err := p2p.NewNode(path)
    // ...
    return C.CString(node.GetPeerID())
}

V Dart probíhá načtení knihovny a vazba funkcí:

Google AdInline article slot
_loadLibrary() {
  if (Platform.isAndroid) {
    _lib = DynamicLibrary.open('libp2p_network.so');
  }
  _startNode = _lib!.lookupFunction<
      Pointer<Utf8> Function(Pointer<Utf8>),
      Pointer<Utf8> Function(Pointer<Utf8>)
  >('StartNode');
}

Volání funkce zahrnuje převod řetězců a ruční správu paměti:

final pathPtr = dir.path.toNativeUtf8();
final resultPtr = _startNode(pathPtr);
final peerId = resultPtr.toDartString();
_freeString(resultPtr);
calloc.free(pathPtr);

Práce s libp2p: vytvoření uzlu a výměna zpráv

Inicializace uzlu zahrnuje nastavení transportů a protokolů:

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(),
    )
    // ...
}

Každé zařízení získává unikátní PeerID – hash veřejného Ed25519 klíče. Odesílání zpráv je realizováno přes streamovou výměnu:

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
}

Zprávy se doručují přímo při online spojení nebo přes reléovou uzlu v offline režimu. Zašifrovaná data se dočasně ukládají, dokud se připojí příjemce.

Zajistění spojení v náročných síťových podmínkách

Pro hlasové volání je implementována třístupňová transportní vrstva:

  • Přímý UDP transport (Pion ICE) – primární kanál s minimální latencí. Výměna ICE kandidátů probíhá přes libp2p zprávy.
  • DCUtR (Hole Punching) – mechanismus propíchnutí spojení přes NAT pomocí Relay.
  • Fallback přes Relay – přenos provozu přes reléovou uzlu, pokud přímé spojení není možné.

Kód pro zpracování audioprůtoku ukazuje práci s vlastním packetovým formátem:

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
}

Příjem dat je organizován v cyklu s rozpoznáním synchronizačních bajtů a zpracováním Opus frameů.

Šifrování a bezpečnost: Double Ratchet v praxi

Pro soukromé chaty je implementován protokol Double Ratchet s Perfect Forward Secrecy a Post-Compromise Security. Klíčové vlastnosti:

  • Každá zpráva je šifrována unikátním klíčem
  • Periodická rotace klíčů
  • Ochrana před kompromitací dlouhodobých klíčů

Systém zajišťuje end-to-end šifrování bez předávání klíčů přes mezilehlé uzly. Všechny kryptografické operace probíhají na zařízení uživatele, což vylučuje úniky při ukládání dat na reléových uzlech.

Zpracování offline scénářů bez kompromisu soukromí

Push notifikace slouží výhradně jako spouštěče pro probuzení aplikace. Důležité je, že:

  • Push zprávy neobsahují data zpráv
  • Neobsahují informace o odesílateli
  • Spouštěč obsahuje pouze služebný příznak

Postup práce:

  • Přijde silent push notifikace
  • OS krátce aktivuje aplikaci
  • Go uzel se připojí k síti a stáhne zašifrované pakety
  • Dešifrování probíhá lokálně
  • Vytvoří se lokální notifikace pro uživatele

Tento přístup zachovává E2EE i při použití centralizovaných push služeb.

Co je důležité

  • FFI most vyžaduje striktní správu paměti: úniky vznikají při vynechání volání FreeString
  • Circuit Relay v2 je klíčový pro obcházení NAT, ale zvyšuje latenci o 15–20 %
  • Double Ratchet zajišťuje PFS, ale vyžaduje synchronizaci stavu mezi zařízeními
  • Push spouštěče neporušují soukromí, protože neobsahují užitečné data
  • Sestavení pro iOS vyžaduje ruční sloučení archivů pro simulátor a zařízení

— Editorial Team

Advertisement 728x90

Číst dál