25 de septiembre de 2024

Blockchain + .NET MAUI ❤️ Una experiencia de trabajo con la autenticación

Este blog analiza la integración de la autenticación de cadena de bloques mediante Ethereum con una aplicación.NET MAUI, centrándose en el inicio de sesión seguro con WalletConnect y el protocolo SIWE. Ofrece información sobre la autenticación descentralizada y los desafíos técnicos a los que se enfrentó durante el desarrollo.
Image UXDIvers blog

Visión general

En los últimos años, la tecnología blockchain ha revolucionado varias industrias al proporcionar soluciones descentralizadas, seguras y transparentes. A medida que las empresas buscan aprovechar cada vez más el potencial de la cadena de bloques, es esencial integrarla con marcos de desarrollo modernos como .NET MAUI. Sin embargo, la integración de.NET MAUI con el ecosistema de Ethereum es todavía incipiente y hay poca experiencia trabajando con estas dos tecnologías. Este artículo explora nuestra experiencia en el uso de la cadena de bloques para la autenticación dentro de una aplicación .NET MAUI, centrándose en la integración del protocolo «Sign-In with Ethereum» (SIWE). Nos adentramos en el proceso de conectar una aplicación de MAUI con carteras de Ethereum mediante WalletConnect, y destacamos cómo este enfoque mejora la seguridad y la experiencia del usuario al permitir a los usuarios autenticarse sin depender de las credenciales tradicionales.

Introducción

La cadena de bloques es una tecnología que se creó en 2008 como base de Bitcoin, la primera criptomoneda. Básicamente, se trata de una base de datos que solo se puede agregar, lo que significa que los datos se pueden agregar pero no eliminar ni modificar, lo que garantiza su inmutabilidad. Este sistema de registro distribuido se mantiene a través de una red de nodos descentralizados, lo que elimina la necesidad de intermediarios y reduce el riesgo de manipulación o censura. Además, la cadena de bloques ofrece cierto grado de seudónimo, ya que las transacciones y los datos se asocian a direcciones criptográficas en lugar de a identidades personales, lo que protege la privacidad de los usuarios y, al mismo tiempo, mantiene la transparencia del sistema.

Inicialmente, las cadenas de bloques se diseñaron para soportar criptomonedas como Bitcoin, proporcionando un sistema seguro y descentralizado para realizar transacciones financieras. Sin embargo, con el tiempo, la tecnología blockchain ha evolucionado y ampliado su alcance, encontrando aplicaciones en varios sectores más allá de las finanzas, como la gestión de la cadena de suministro, la salud, el arte y las identidades digitales. Hoy en día, las cadenas de bloques también se utilizan para la autenticación, lo que permite a las personas verificar su identidad de forma segura y descentralizada, lo que elimina la necesidad de depender de intermediarios tradicionales y mejora la privacidad y el control de los datos personales.

El área de la autenticación con blockchain es muy amplia e incluye métodos avanzados como las credenciales anónimas. Estas credenciales permiten verificar la identidad sin revelar información personal mediante el uso de técnicas criptográficas, como las pruebas de conocimiento cero. Esto garantiza transacciones seguras y privadas, eliminando la necesidad de intermediarios y permitiendo a los usuarios mantener el control sobre sus datos personales en entornos digitales descentralizados. Estas credenciales, por ejemplo, se pueden usar para superar los desafíos que plantea la interoperación de cadenas de bloques con y sin permiso, como se ha explicado en este artículo Soy coautor con otros colegas.

Desafío

En este artículo, utilizaremos la cadena de bloques de Ethereum para autenticar a un usuario en nuestra aplicación.NET MAUI utilizando su monedero. Este concepto se basa en ERC-4361: Iniciar sesión con Ethereum. SIWE es un protocolo que permite a los usuarios iniciar sesión en aplicaciones web utilizando sus direcciones de Ethereum, eliminando la necesidad de nombres de usuario y contraseñas tradicionales. Al firmar un mensaje con su clave privada, los usuarios pueden demostrar que son propietarios de una dirección de Ethereum sin exponer sus claves privadas. Esto proporciona un método de autenticación seguro y descentralizado, que mejora la privacidad y la seguridad del usuario al evitar la dependencia de sistemas de autenticación centralizados y vulnerables. Este mecanismo es especialmente adecuado para las aplicaciones web3 en comparación con los métodos tradicionales como OpenID Connect u OAuth2.

Hay miles de carteras en el mercado, e implementar cada uno de sus SDK para conectar nuestra aplicación con todas las posibilidades es inviable. Por lo tanto, hay dos opciones: 1) seleccionar solo las carteras más utilizadas, como TrustWallet, Metamask, etc., y 2) usar WalletConnect (opción que seguiremos en el artículo).

WalletConnect es un protocolo de código abierto que conecta de forma segura las aplicaciones descentralizadas (dApps) con las carteras, lo que permite a los usuarios autorizar transacciones escaneando un código QR o utilizando enlaces profundos sin comprometer sus claves privadas. En este caso, utilizaremos WalletConnectSharp, que es una implementación del protocolo en C#. Puedes consultar su GitHub aquí

Solución

Blockchain Android demo image

Vamos a crear una aplicación de ejemplo utilizando .NET MAUI que permita a los usuarios iniciar sesión con su monedero Ethereum. La aplicación contará con un botón de inicio de sesión que implementa el protocolo de inicio de sesión con Ethereum (SIWE). El primer paso es visitar el WalletConnect en la nube sitio web, inicia sesión y crea una cuenta si no tienes una. Después de iniciar sesión, crea un nuevo proyecto en la plataforma y asegúrate de guardar el ID del proyecto, ya que será necesario más adelante.

A continuación, cree una nueva clase que herede Botón y agrega un comando para gestionar el evento de clic, como este:

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


El Al hacer clic la función contiene la lógica de alto nivel para realizar el proceso de inicio de sesión. Implica conectar la aplicación a la billetera, crear el mensaje de inicio de sesión, solicitar a la billetera que firme ese mensaje y, por último, verificar que la firma es válida. Realizaremos este proceso paso a paso.

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");
}

Conexión con monedero

Echemos un vistazo más de cerca a la ConnectWallet función. Esta función construye dos objetos: Opciones de SignClient y Opciones de conexión. Estos objetos se pueden modificar para personalizar el mensaje que muestra la billetera y ajustar algunas configuraciones de conexión.

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;
}


En el Opciones de SignClient objeto, debe establecer el ID del proyecto utilizando el ID obtenido del Wallet Connect sitio web. También puedes descomentar la propiedad Storage para habilitar el almacenamiento persistente. El objeto de metadatos permite personalizar la apariencia del mensaje del monedero.

Así es como puede configurar el Opciones de SignClient objeto:

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()
};


A continuación se muestra un ejemplo del aspecto que tendrá el mensaje con estas personalizaciones:

Customized wallet message


El Opciones de conexión El objeto especifica la hora de caducidad de la conexión y los espacios de nombres necesarios, que son las primitivas que la cartera debe admitir para establecer la conexión. Aunque el signo_personal Este método es el único método esencial para nuestras necesidades. Incluimos métodos adicionales en este ejemplo para proporcionar una ilustración más completa de las posibles funcionalidades.

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"
                   }
               }
           }
       }
   };
}


Una vez que hayamos creado el Opciones de SignClient y Opciones de conexión objetos, podemos inicializar el cliente, una instancia de Cliente WalletConnect Sign. Este objeto gestiona la comunicación entre la aplicación y la billetera. Después de inicializarlo, llamamos al método Connect para obtener Conectar datos objeto. En este caso, utilizamos un enlace profundo proporcionado por Conectar datos para abrir la aplicación de monedero en el dispositivo. También puedes mostrar un código QR en la pantalla para que el usuario lo escanee con su teléfono. Este segundo enfoque es preferible cuando el usuario realiza la transacción en un terminal y tiene la billetera en un dispositivo diferente. En este ejemplo, utilizamos el enlace profundo para abrir la aplicación con el Lanzador, una clase proporcionada por MAUI para lanzar otras aplicaciones.

Por último, el ConnectWallet el método espera hasta que la billetera apruebe la conexión. Si el usuario no aprueba la conexión o si se agota el tiempo de espera de la conexión, se produce una excepción.

Solicitud de tamaño

Ahora que hemos establecido una conexión con la billetera, es el momento de realizar la solicitud de inicio de sesión. El Iniciar sesión con Ethereum la función es responsable de esta tarea. Comienza con la creación de una instancia del Mensaje SIWE clase.

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);
}


El Mensaje SIWE La clase representa un mensaje de «Inicio de sesión con Ethereum» que se utiliza para autenticar a los usuarios al verificar su control sobre una dirección de Ethereum en particular. Esta clase incluye varias propiedades, tales como Dominio, Dirección, Declaración, Aud (audiencia), ID de cadena, Nonce, Caducidad, Versión, y Iat (emitido en su momento). El Nonce y Caducidad los parámetros desempeñan un papel crucial en la seguridad al impedir los ataques de repetición y garantizar que el mensaje firmado solo sea válido durante un período limitado. El protocolo podría mejorarse incorporando medidas de seguridad adicionales, como especificar el intervalo de tiempo permitido para la validez de la firma e implementar una validación más estricta de la Dominio y Aud campos para evitar ataques de suplantación de identidad.

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();
}


El siguiente paso es definir la solicitud. En este caso, vamos a utilizar el 'signo_personal' método. En los documentos de WalletConnect Ethereum | WalletConnect Docs hay un resumen de los métodos RPC de Ethereum y cómo realizar la solicitud y cuáles son las respuestas de cada método. Utilizando el signo_personal El método para firmar los mensajes de «Inicio de sesión con Ethereum» (SIWE) es el enfoque estándar en la industria debido a su formato fácil de usar y su seguridad mejorada. Este método permite a los usuarios firmar un mensaje claro y legible, lo que reduce el riesgo de uso indebido accidental y deja claro que no están firmando una transacción. Su amplio soporte entre las carteras de Ethereum garantiza la compatibilidad y una experiencia de usuario uniforme. Además, el prefijo agregado ayuda a prevenir los ataques de repetición al distinguir claramente los mensajes firmados de las transacciones.

Como se explica en la documentación, definimos Signo personal ETH clase para seguir el RPC para signo_personal método. Esta clase recibe el mensaje y la dirección de usuario como parámetros en su constructor. Como el convertidor JSON requiere un constructor sin parámetros, se agregó un constructor adicional para cumplir con este requisito.

[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()
   {
      
   }
}

Por último, el método vuelve a abrir el monedero mediante el Lanzador clase y envía la solicitud a la billetera. La respuesta contiene la firma del mensaje por parte del usuario, que luego la función devuelve junto con el mensaje SIWE original. Esta información será utilizada por el Verificar la firma de firma de firma personal función para validar la firma.

Validación

El Verificar la firma de firma de firma personal la función comprueba si una determinada firma de Ethereum fue creada por una dirección de Ethereum específica utilizando el Neethereum biblioteca https://nethereum.com/. Toma un mensaje, una firma y una dirección esperada como entradas. La función usa un Firmante de mensajes de Ethereum para codificar el mensaje en UTF-8 y recuperar la dirección Ethereum de firma de la firma mediante la recuperación de curvas elípticas. A continuación, compara esta dirección recuperada con la dirección esperada sin distinguir entre mayúsculas y minúsculas. Si coinciden, la función devuelve cierto, confirmando que la firma es válida y que fue generada por el propietario de la dirección esperada; de lo contrario, devuelve falso, indicando que la firma no es válida o proviene de una dirección diferente.

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);
}

Una vez validada la firma, el Al hacer clic la función llama al Comando iniciado sesión. El usuario define este comando mediante una propiedad enlazable y recibe una instancia del Datos de usuario autenticados clase como parámetro de comando. El Datos de usuario autenticados la clase es personalizada y puede contener cualquier información necesaria para el caso de uso específico.

//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;
}


Problemas y limitaciones

La creación de un ejemplo de SIWE con.NET MAUI supuso un desafío debido a la limitada documentación disponible para Wallet Connect Sharp biblioteca. Además, si bien hay un paquete llamado WalletConnect Sharp.auth diseñadas específicamente para la autenticación de carteras, la mayoría de las carteras no eran compatibles con este protocolo. Como resultado, optamos por usar el signo_personal método, que es ampliamente compatible con las carteras. Sin embargo, este método requiere que la aplicación de monedero se abra dos veces, lo que, aunque es un patrón común en las aplicaciones web3, puede resultar inconveniente para los usuarios que no están familiarizados con el ecosistema.

La solución también se comporta de forma diferente según la plataforma. En Android, cuando la clase Launcher abre un enlace profundo, el sistema operativo muestra un cuadro de diálogo que permite al usuario elegir entre las carteras disponibles. Sin embargo, en iOS, esta funcionalidad no existe, por lo que el sistema operativo abrirá la aplicación de monedero predeterminada sin preguntar al usuario cuál prefiere.

Blockchain iOS demo image

Trabajo futuro

Hay un nuevo SDK de WalletConnect llamado AppKit (Descripción general | WalletConnect Docs), que ofrece funciones como la posibilidad de solicitar la autenticación del monedero con una sola interacción. La documentación de este SDK es completa y fácil de entender. Sin embargo, el SDK no está disponible para .NET MAUI, pero sí para iOS y Android. Se pueden crear enlaces de biblioteca para MAUI a partir de estos paquetes específicos de la plataforma.

Otra posible mejora futura es ampliar esta solución mediante la creación de una solución más compleja Mensaje SIWE que mejora la seguridad. Además, la línea nonce podría usarse para vincular el mensaje a una sesión y trabajar con los tokens JWT. Hay un ejemplo de SIWE usando Blazor que integra JWT y la biblioteca Neethereum, que se puede encontrar en GitHub aquí

Conclusiones

Creamos con éxito una aplicación de ejemplo en .NET MAUI que administra una conexión con una aplicación de cartera utilizando solo bibliotecas de C#, sin ninguna dependencia de JavaScript. Esta aplicación permite a los usuarios iniciar sesión con su monedero mediante el protocolo SIWE. Toda la lógica relacionada con el protocolo y la conexión está encapsulada en un solo control, lo que facilita su reutilización en otras soluciones, ya que puedes comprobar todo el código aquí

La cadena de bloques es una tecnología poderosa que puede mejorar las soluciones de software al brindar seguridad, transparencia y confianza. Aunque recientemente se ha visto eclipsada por el auge de la IA, la cadena de bloques sigue siendo una herramienta valiosa. Como los sistemas actuales se integran cada vez más con la IA, también se debe considerar la posibilidad de integrar la cadena de bloques, ya que puede ser un poderoso diferenciador en cualquier sector empresarial vertical.

En UXDivers, nos comprometemos a ofrecer una UI/UX excepcional en todas las tecnologías.NET. La integración de herramientas innovadoras como la cadena de bloques es solo una de las formas en que ayudamos a nuestros clientes a mantenerse a la vanguardia, lo que permite interacciones seguras, transparentes y preparadas para el futuro con la tecnología. A medida que continuamos explorando tecnologías de vanguardia, nos entusiasma sobrepasar los límites y ofrecer soluciones que no solo satisfagan las necesidades de hoy, sino que también abran las posibilidades del mañana. ¡Estén atentos mientras profundizamos en las tendencias emergentes y descubrimos nuevas formas de aportar aún más valor a sus proyectos! 🚀

Copy link
¡Estamos entusiasmados con los nuevos proyectos!

¿Tienes un proyecto en mente?
Pongámonos a trabajar.

Comenzar un proyecto