25. September 2024

Blockchain + .NET MAUI ❤️ Eine Erfahrung in der Arbeit mit Authentifizierung

In diesem Blog wird die Integration der Blockchain-Authentifizierung mithilfe von Ethereum in eine.NET-MAUI-App erörtert, wobei der Schwerpunkt auf der sicheren Anmeldung mit WalletConnect und dem SIWE-Protokoll liegt. Es bietet Einblicke in die dezentrale Authentifizierung und die technischen Herausforderungen, denen sich die Entwicklung gegenübersieht.
Image UXDIvers blog

Überblick

In den letzten Jahren hat die Blockchain-Technologie verschiedene Branchen durch die Bereitstellung dezentraler, sicherer und transparenter Lösungen revolutioniert. Da Unternehmen zunehmend versuchen, das Potenzial der Blockchain auszuschöpfen, wird ihre Integration in moderne Entwicklungsframeworks wie.NET MAUI unerlässlich. Die Integration von.NET MAUI in das Ethereum-Ökosystem steckt jedoch noch in den Kinderschuhen, und es gibt wenig Erfahrung mit diesen beiden Technologien. In diesem Artikel werden unsere Erfahrungen mit der Verwendung von Blockchain zur Authentifizierung innerhalb einer.NET-MAUI-Anwendung untersucht, wobei der Schwerpunkt auf der Integration des „Sign-In with Ethereum“ (SIWE) -Protokolls liegt. Wir beschäftigen uns mit dem Prozess der Verbindung einer MAUI-App mit Ethereum-Wallets mithilfe von WalletConnect und heben hervor, wie dieser Ansatz die Sicherheit und das Benutzererlebnis verbessert, indem er es Benutzern ermöglicht, sich zu authentifizieren, ohne sich auf herkömmliche Anmeldeinformationen verlassen zu müssen.

Einführung

Blockchain ist eine Technologie, die 2008 als Grundlage für Bitcoin, die erste Kryptowährung, entwickelt wurde. Im Wesentlichen handelt es sich um eine Datenbank, die nur zum Anhängen verwendet werden kann, was bedeutet, dass Daten hinzugefügt, aber nicht gelöscht oder geändert werden können, wodurch ihre Unveränderlichkeit gewährleistet wird. Dieses verteilte Ledger-System wird über ein Netzwerk dezentraler Knoten verwaltet, sodass keine Vermittler erforderlich sind und das Risiko von Manipulationen oder Zensur verringert wird. Darüber hinaus bietet die Blockchain ein gewisses Maß an Pseudonymität, da Transaktionen und Daten eher mit kryptografischen Adressen als mit persönlichen Identitäten verknüpft werden, wodurch die Privatsphäre der Nutzer geschützt und gleichzeitig die Transparenz des Systems gewahrt bleibt.

Ursprünglich wurden Blockchains entwickelt, um Kryptowährungen wie Bitcoin zu unterstützen und ein sicheres und dezentrales System für die Durchführung von Finanztransaktionen bereitzustellen. Im Laufe der Zeit hat sich die Blockchain-Technologie jedoch weiterentwickelt und ihren Anwendungsbereich erweitert. Sie fand auch in verschiedenen Bereichen außerhalb des Finanzwesens Anwendung, z. B. in den Bereichen Lieferkettenmanagement, Gesundheit, Kunst und digitale Identitäten. Heutzutage werden Blockchains auch zur Authentifizierung verwendet, sodass Einzelpersonen ihre Identität sicher und dezentral verifizieren können. Dadurch entfällt die Notwendigkeit, sich auf traditionelle Vermittler zu verlassen, und der Datenschutz und die Kontrolle über personenbezogene Daten werden verbessert.

Der Bereich der Authentifizierung mit Blockchain ist sehr breit gefächert und umfasst fortgeschrittene Methoden wie anonyme Anmeldeinformationen. Diese Anmeldeinformationen ermöglichen eine Identitätsprüfung, ohne personenbezogene Daten preiszugeben, indem kryptografische Techniken wie Zero-Knowledge-Proofs verwendet werden. Dies gewährleistet sichere und private Transaktionen, macht Vermittler überflüssig und ermöglicht es den Benutzern, die Kontrolle über ihre persönlichen Daten in dezentralen digitalen Umgebungen zu behalten. Diese Anmeldeinformationen können beispielsweise verwendet werden, um die Herausforderungen bei der Interoperabilität von Blockchains mit und ohne Genehmigung zu bewältigen, wie erklärt in diesem Artikel Ich habe zusammen mit anderen Kollegen verfasst.

Herausforderung

In diesem Artikel werden wir die Ethereum-Blockchain verwenden, um einen Benutzer in unserer .NET MAUI-App mithilfe seiner Wallet zu authentifizieren. Dieses Konzept basiert auf ERC-4361: Mit Ethereum anmelden. SIWE ist ein Protokoll, mit dem sich Benutzer mit ihren Ethereum-Adressen bei Webanwendungen anmelden können, sodass keine herkömmlichen Benutzernamen und Passwörter erforderlich sind. Durch das Signieren einer Nachricht mit ihrem privaten Schlüssel können Benutzer nachweisen, dass sie Eigentümer einer Ethereum-Adresse sind, ohne ihre privaten Schlüssel preiszugeben. Dies bietet eine sichere und dezentrale Authentifizierungsmethode, die den Datenschutz und die Sicherheit der Benutzer verbessert, indem vermieden wird, dass sie sich auf zentralisierte und anfällige Authentifizierungssysteme verlassen müssen. Dieser Mechanismus eignet sich im Vergleich zu herkömmlichen Methoden wie OpenID Connect oder OAuth2 besonders gut für Web3-Anwendungen.

Es gibt Tausende von Wallets auf dem Markt, und die Implementierung jedes ihrer SDKs, um unsere App mit allen Möglichkeiten zu verbinden, ist nicht machbar. Daher gibt es zwei Möglichkeiten: 1) Wählen Sie nur die am häufigsten verwendeten Wallets wie TrustWallet, Metamask usw. aus und 2) verwenden Sie WalletConnect (Option, auf die wir im Artikel eingehen werden).

WalletConnect ist ein Open-Source-Protokoll, das dezentrale Anwendungen (DApps) sicher mit Wallets verbindet und es Benutzern ermöglicht, Transaktionen zu autorisieren, indem sie einen QR-Code scannen oder Deeplinks verwenden, ohne ihre privaten Schlüssel zu gefährden. In diesem Fall verwenden wir WalletConnectSharp, eine Implementierung des Protokolls in C#. Sie können es auf GitHub überprüfen hier

Lösung

Blockchain Android demo image

Wir werden eine Beispielanwendung mit .NET MAUI erstellen, mit der sich Benutzer mit ihrer Ethereum-Wallet anmelden können. Die Anwendung wird über eine Anmeldeschaltfläche verfügen, die das SIWE-Protokoll (Sign-In with Ethereum) implementiert. Der erste Schritt ist der Besuch der WalletConnect Cloud Website, melden Sie sich an und erstellen Sie ein Konto, falls Sie noch keines haben. Erstellen Sie nach dem Einloggen ein neues Projekt auf der Plattform und speichern Sie unbedingt die Projekt-ID, da sie später benötigt wird.

Als Nächstes erstellen Sie eine neue Klasse, die erbt von Taste und fügen Sie einen Befehl hinzu, um das Klickereignis wie folgt zu behandeln:

public WalletConnectButton()
{   
    Text = "Sign in with WalletConnect";   
  
    Command = new Command(OnClicked);
}


Das Bei geklickt Die Funktion enthält die allgemeine Logik zur Durchführung des Anmeldevorgangs. Dazu wird die App mit der Wallet verbunden, die Anmeldenachricht erstellt, die Wallet aufgefordert, diese Nachricht zu signieren, und schließlich wird überprüft, ob die Signatur gültig ist. Wir werden diesen Prozess Schritt für Schritt durchgehen.

private async void OnClicked()
{
   //Connects the app with the wallet.
   var connected = await ConnectWallet();
   if (!connected)
   {
       return;
   }
  
   //User's wallet address
   var address = _dappClient.AddressProvider.DefaultSession.CurrentAddress(CHAIN_ID).Address;
      
   //Return the siweMessage and the signature
   var (signature, siweMessage) = await SignInWithEthereum(address);


   if (signature == null || siweMessage == null)
   {
       var errorMsg = "Error: An error occurred signing in with ethereum";
       Debug.WriteLine(errorMsg);
       ErrorCommand?.Execute(errorMsg);
       return;
   }


   //Verifies that the address that signs the SIWE message is the user's address.
   var isValid = VerifyPersonalSignSignature(siweMessage.ToString(), signature, address);
   if (isValid)
   {
       Debug.WriteLine("Success: User authenticated with Ethereum");
       SignedInCommand?.Execute(new AutenticatedUserData
       {
           AuthenticationNonce = siweMessage.Nonce,
           UserAddress = address,
           WalletName = _dappClient.AddressProvider.DefaultSession.Peer.Metadata.Name,
           UserPublicKey = _dappClient.AddressProvider.DefaultSession.Peer.PublicKey
       });
       return;
   }
      
   ErrorCommand?.Execute("The wallet signature is not valid");
   Debug.WriteLine("Error: The wallet signature is not valid");
}

Wallet-Verbindung

Schauen wir uns das genauer an Wallet verbinden Funktion. Diese Funktion konstruiert zwei Objekte: SignClient-Optionen und Verbindungsoptionen. Diese Objekte können geändert werden, um die von der Wallet angezeigte Nachricht anzupassen und einige Verbindungseinstellungen anzupassen.

private async Task<bool> ConnectWallet()
{
   //Modify this function to customize the client options.
   var dappOptions = CreateSignClientOptions();


   //Modify this function to customize the connection options.
   var dappConnectOptions = CreateConnectionOptions();


   //Initiates the client
   _dappClient = await WalletConnectSignClient.Init(dappOptions);
   var connectData = await _dappClient.Connect(dappConnectOptions);


   //Launch the wallet app with the connection params
   //On android the OS will let you choose the wallet app but on iOS it won't
   var uri = new Uri(connectData.Uri);
   await Launcher.Default.OpenAsync(uri);
  
   try
   {
       //Wait until the user approves the connection
       //If the connection is not approved or it times out an exception will be thrown
       await connectData.Approval;
      
       Debug.WriteLine("Approved");
       return true;
   }
   catch (Exception e)
   {
       Debug.WriteLine(e.Message);
       ErrorCommand?.Execute(e.Message);
   }


   return false;
}


In der SignClient-Optionen Objekt, du musst das setzen Projekt-ID unter Verwendung der ID, die von der Geldbörse Connect Webseite. Sie können die Eigenschaft Storage auch auskommentieren, um persistenten Speicher zu aktivieren. Mit dem Metadatenobjekt können Sie das Erscheinungsbild der Wallet-Nachricht anpassen.

So können Sie das einrichten SignClient-Optionen Objekt:

return new SignClientOptions()
{
   ProjectId = WALLET_CONNECT_PROJECT_ID,
   Metadata = new Metadata()
   {
       Description = "MAUI WalletConnect and SIWE example",
       Icons = new[]
       {
           "https://cdn.prod.website-files.com/630fae9a46ee72ef23481b76/643471939ca472a0f0fa96b6_favicon.png"
       },
       Name = "MAUI SIWE Example",
       Url = "https://uxdivers.com/"
   },
   Storage = new InMemoryStorage()
};


Im Folgenden finden Sie ein Beispiel dafür, wie die Nachricht mit diesen Anpassungen aussehen wird:

Customized wallet message


Das Verbindungsoptionen object gibt die Ablaufzeit der Verbindung und die erforderlichen Namespaces an. Dies sind die Primitiven, die das Wallet unterstützen muss, um die Verbindung herzustellen. Obwohl die persönliches_Schild Methode ist die einzig wichtige Methode für unsere Bedürfnisse. Wir fügen in diesem Beispiel zusätzliche Methoden hinzu, um mögliche Funktionen umfassender zu veranschaulichen.

private ConnectOptions CreateConnectionOptions()
{
   return new ConnectOptions()
   {
       Expiry = (long)new TimeSpan(0,10,0).TotalSeconds,
       RequiredNamespaces = new RequiredNamespaces()
       {
           {
               "eip155", new ProposedNamespace()
               {
                   Methods = new[]
                   {
                       "eth_sendTransaction", "eth_signTransaction", "eth_sign", "personal_sign", "eth_signTypedData",
                   },
                   Chains = new[]
                   {
                       CHAIN_ID
                   },
                   Events = new[]
                   {
                       "chainChanged", "accountsChanged", "connect", "disconnect"
                   }
               }
           }
       }
   };
}


Sobald wir die erstellt haben SignClient-Optionen und Verbindungsoptionen Objekte, wir können den Client initialisieren, eine Instanz von WalletConnect Sign-Client. Dieses Objekt verwaltet die Kommunikation zwischen der App und der Wallet. Nach der Initialisierung rufen wir die Connect-Methode auf, um die Daten verbinden Objekt. In diesem Fall verwenden wir einen Deep-Link von Daten verbinden um die Wallet-App auf dem Gerät zu öffnen. Alternativ können Sie auf dem Bildschirm einen QR-Code anzeigen, den der Benutzer mit seinem Telefon scannen kann. Dieser zweite Ansatz ist vorzuziehen, wenn der Benutzer die Transaktion auf einem Terminal durchführt und die Brieftasche auf einem anderen Gerät hat. In diesem Beispiel verwenden wir den Deeplink, um die App mit dem zu öffnen Launcher, eine von MAUI bereitgestellte Klasse, um andere Anwendungen zu starten.

Schließlich die Wallet verbinden Die Methode wartet, bis das Wallet die Verbindung genehmigt. Wenn der Benutzer die Verbindung nicht genehmigt oder wenn bei der Verbindung ein Timeout auftritt, wird eine Ausnahme ausgelöst.

Anfrage stellen

Nachdem wir eine Verbindung mit der Wallet hergestellt haben, ist es an der Zeit, die Anmeldeanfrage zu stellen. Das Melde dich mit Ethereum an Die Funktion ist für diese Aufgabe verantwortlich. Sie beginnt mit der Erstellung einer Instanz von SIWE-Nachricht Klasse.

private async Task<(string, SiweMessage)> SignInWithEthereum(string address)
{
   //Creates the SIWE message that will be signed by the user's wallet
   var siweMessage = CreateSiweMessage();


   //Create the request following the RPC format for 'personal_sign'
   var request = new EthPersonalSign(siweMessage.ToString(), address);


   //Launches the wallet app
   Application.Current.Dispatcher.Dispatch(async () => await Launcher.Default.OpenAsync($"wc:"));
  
   string signature;
   try
   {
       //Send the sign request to the user's wallet with the siwe message
       signature = await _dappClient.Request<EthPersonalSign, string>(_dappClient.AddressProvider.DefaultSession.Topic, request, CHAIN_ID, siweMessage.Expiry);
   }
   catch (Exception e)
   {
       signature = null;
       siweMessage = null;
   }
  
   return (signature, siweMessage);
}


Das SIWE-Nachricht Klasse steht für eine „Sign-In with Ethereum“ -Nachricht, die verwendet wird, um Benutzer zu authentifizieren, indem ihre Kontrolle über eine bestimmte Ethereum-Adresse überprüft wird. Diese Klasse umfasst verschiedene Eigenschaften wie Domäne, Adresse, Aussage, Aud (Publikum), Ketten-ID, Nonce, Ablauf, Version, und Iat (zeitgleich ausgestellt). Das Nonce und Ablauf Parameter spielen eine entscheidende Rolle bei der Sicherheit, da sie Wiederholungsangriffe verhindern und sicherstellen, dass die signierte Nachricht nur für einen begrenzten Zeitraum gültig ist. Das Protokoll könnte durch zusätzliche Sicherheitsmaßnahmen verbessert werden, wie etwa die Festlegung des zulässigen Zeitfensters für die Gültigkeit der Signatur und die Einführung einer strengeren Validierung der Domäne und Aud Felder zur Verhinderung von Phishing-Angriffen.

public class SiweMessage
{
   public string Domain { get; set; }
   public string Address { get; set; }
   public string Statement { get; set; }
   public string Aud { get; set; }
   public string ChainId { get; set; }
   public string Nonce { get; set; }
   public long? Expiry { get; set; }
   public string Version => "1";
   public string Iat { get; } = DateTime.Now.ToISOString();
}


Der nächste Schritt besteht darin, die Anfrage zu definieren. In diesem Fall verwenden wir das 'persönliches_Zeichen' Methode. In den WalletConnect-Dokumenten Ethereum | WalletConnect-Dokumente Es gibt eine Zusammenfassung der Ethereum RPC-Methoden und wie die Anfrage gestellt wird und wie die Antworten der einzelnen Methoden lauten. Mit dem persönliches_Schild Die Methode zum Signieren von „Sign-In with Ethereum“ (SIWE) -Nachrichten ist aufgrund ihres benutzerfreundlichen Formats und der erhöhten Sicherheit der Standardansatz in der Branche. Mit dieser Methode können Benutzer eine klare, lesbare Nachricht signieren, wodurch das Risiko eines versehentlichen Missbrauchs verringert und deutlich gemacht wird, dass sie keine Transaktion signieren. Die breite Unterstützung unter den Ethereum-Wallets gewährleistet Kompatibilität und eine konsistente Benutzererfahrung. Darüber hinaus verhindert das hinzugefügte Präfix Wiederholungsangriffe, indem signierte Nachrichten klar von Transaktionen unterschieden werden.

Wie in der Dokumentation erklärt, definieren wir ETH-Personalschild Klasse, die dem RPC für den folgt persönliches_Schild Methode. Diese Klasse empfängt die Nachricht und die Benutzeradresse als Parameter in ihrem Konstruktor. Da der JSON-Konverter einen parameterlosen Konstruktor benötigt, wurde ein zusätzlicher Konstruktor hinzugefügt, um dieser Anforderung gerecht zu werden.

[RpcMethod("personal_sign"), RpcRequestOptions(Clock.ONE_MINUTE, 99998)]
public class EthPersonalSign : List<string>
{
   public EthPersonalSign(string message, string address) : base(new List<string> { message, address })
   {
      
   }


   public EthPersonalSign()
   {
      
   }
}

Schließlich öffnet die Methode die Brieftasche erneut mit dem Launcher Klasse und sendet die Anfrage an die Wallet. Die Antwort enthält die Signatur der Nachricht durch den Benutzer, die später von der Funktion als Paar mit der ursprünglichen SIWE-Nachricht zurückgegeben wird. Diese Information wird von der verwendet Überprüfen Sie die persönliche Sign-Signatur Funktion zur Validierung der Signatur.

Validierung

Das Überprüfen Sie die persönliche Sign-Signatur Die Funktion überprüft, ob eine bestimmte Ethereum-Signatur von einer bestimmten Ethereum-Adresse erstellt wurde, und verwendet dabei Nevere https://nethereum.com/ Bibliothek. Es verwendet eine Nachricht, eine Signatur und eine erwartete Adresse als Eingaben. Die Funktion verwendet eine Ethereum-Nachrichtensignierer um die Nachricht in UTF-8 zu kodieren und die signierende Ethereum-Adresse mithilfe der elliptischen Kurvenwiederherstellung aus der Signatur wiederherzustellen. Anschließend wird diese wiederhergestellte Adresse ohne Berücksichtigung der Groß- und Kleinschreibung mit der erwarteten Adresse verglichen. Wenn sie übereinstimmen, kehrt die Funktion zurück wahr, um zu bestätigen, dass die Signatur gültig ist und vom erwarteten Adressbesitzer generiert wurde; andernfalls wird zurückgegeben falsch, was darauf hinweist, dass die Signatur ungültig ist oder von einer anderen Adresse stammt.

public bool VerifyPersonalSignSignature(string message, string signature, string expectedAddress)
{
   // Recover the address from the signature
   var signer = new EthereumMessageSigner();
   var recoveredAddress = signer.EncodeUTF8AndEcRecover(message, signature);


   // Compare the recovered address with the expected address
   return string.Equals(recoveredAddress, expectedAddress, StringComparison.OrdinalIgnoreCase);
}

Sobald die Signatur validiert ist, Bei geklickt Funktion ruft die Befehl „Ansigniert“. Dieser Befehl wird vom Benutzer über eine bindbare Eigenschaft definiert und erhält eine Instanz von Authentifizierte Benutzerdaten Klasse als Befehlsparameter. Die Authentifizierte Benutzerdaten Die Klasse ist benutzerdefiniert und kann alle Informationen enthalten, die für den spezifischen Anwendungsfall benötigt werden.

//Verifies that the address that signs the SIWE message is the user's address.
var isValid = VerifyPersonalSignSignature(siweMessage.ToString(), signature, address);
if (isValid)
{
   Debug.WriteLine("Success: User authenticated with Ethereum");
   SignedInCommand?.Execute(new AutenticatedUserData
   {
       AuthenticationNonce = siweMessage.Nonce,
       UserAddress = address,
       WalletName = _dappClient.AddressProvider.DefaultSession.Peer.Metadata.Name,
       UserPublicKey = _dappClient.AddressProvider.DefaultSession.Peer.PublicKey
   });
   return;
}


Probleme und Einschränkungen

Die Erstellung eines SIWE-Beispiels mit.NET MAUI war aufgrund der begrenzten verfügbaren Dokumentation für die Geldbörse Connect Sharp Bibliothek. Zusätzlich gibt es zwar ein Paket namens Brieftasche ConnectSharp.Auth Die meisten Wallets wurden speziell für die Wallet-Authentifizierung entwickelt und waren mit diesem Protokoll nicht kompatibel. Aus diesem Grund haben wir uns für die Verwendung des entschieden persönliches_Schild Methode, die von Wallets weithin unterstützt wird. Bei dieser Methode muss die Wallet-App jedoch zweimal geöffnet werden. Dies ist zwar ein übliches Muster in Web3-Anwendungen, kann jedoch für Benutzer, die mit dem Ökosystem nicht vertraut sind, unpraktisch sein.

Die Lösung verhält sich auch je nach Plattform unterschiedlich. Wenn auf Android ein Deeplink von der Launcher-Klasse geöffnet wird, zeigt das Betriebssystem einen Dialog an, in dem der Benutzer aus den verfügbaren Wallets auswählen kann. Unter iOS ist diese Funktion jedoch nicht vorhanden, sodass das Betriebssystem die Standard-Wallet-App öffnet, ohne den Benutzer zu fragen, welche er bevorzugt.

Blockchain iOS demo image

Zukünftige Arbeit

Es gibt ein neues WalletConnect SDK namens AppKit (Überblick | WalletConnect Docs), das Funktionen wie die Möglichkeit bietet, mit nur einer Interaktion eine Wallet-Authentifizierung anzufordern. Die Dokumentation für dieses SDK ist umfassend und leicht verständlich. Das SDK ist jedoch nicht für .NET MAUI verfügbar, sondern für iOS und Android. Aus diesen plattformspezifischen Paketen könnten Bibliotheksbindungen für MAUI erstellt werden.

Eine weitere mögliche zukünftige Verbesserung besteht darin, diese Lösung um eine komplexere Lösung zu erweitern SIWE-Nachricht das verbessert die Sicherheit. Zusätzlich könnte die Nonce-Zeile verwendet werden, um die Nachricht mit einer Sitzung zu verknüpfen und mit JWT-Token zu arbeiten. Es gibt ein Beispiel dafür, wie SIWE Blazor verwendet, das JWT und die Nethere-Bibliothek integriert, die auf GitHub zu finden ist hier

Schlüsse

Wir haben erfolgreich eine Beispiel-App in.NET MAUI erstellt, die eine Verbindung mit einer Wallet-App nur mithilfe von C#-Bibliotheken ohne JavaScript-Abhängigkeiten verwaltet. Mit dieser App können sich Benutzer mithilfe des SIWE-Protokolls mit ihrer Wallet anmelden. Die gesamte Logik, die sich auf das Protokoll und die Verbindung bezieht, ist in einem einzigen Steuerelement zusammengefasst, sodass sie leicht in anderen Lösungen wiederverwendet werden kann. Sie können den gesamten Code überprüfen hier

Blockchain ist eine leistungsstarke Technologie, die Softwarelösungen verbessern kann, indem sie Sicherheit, Transparenz und Vertrauen bietet. Obwohl Blockchain in letzter Zeit vom Aufstieg der KI überschattet wurde, bleibt sie ein wertvolles Instrument. Da aktuelle Systeme zunehmend in KI integriert werden, sollte auch Blockchain für die Integration in Betracht gezogen werden, da sie in jeder Geschäftsbranche ein starkes Unterscheidungsmerkmal sein kann.

Bei UXDivers sind wir bestrebt, außergewöhnliche UI/UX für alle .NET-Technologien bereitzustellen. Die Integration innovativer Tools wie Blockchain ist nur eine Möglichkeit, unseren Kunden zu helfen, der Konkurrenz immer einen Schritt voraus zu sein und sichere, transparente und zukunftssichere Interaktionen mit Technologie zu ermöglichen. Während wir weiterhin Spitzentechnologien erforschen, freuen wir uns darauf, Grenzen zu überschreiten und Lösungen zu liefern, die nicht nur die Anforderungen von heute erfüllen, sondern auch die Möglichkeiten von morgen erschließen. Bleiben Sie dran, während wir uns eingehender mit neuen Trends befassen und neue Wege entdecken, um Ihren Projekten noch mehr Wert zu verleihen! 🚀

Copy link
Wir freuen uns auf neue Projekte!

Haben Sie ein Projekt im Sinn?
Lass uns an die Arbeit gehen.

Starte ein Projekt