Tiempo estimado: 10-20 minutos

En esta guía le mostraremos cómo usar el código fuente proporcionado para un contrato basado en EVM y explicaremos el funcionamiento interno de un contrato verificador de mensajes personalizado en cadena que compone el Universal Signature Validator (ERC-6492), y cómo realizar la verificación de firmas en la aplicación usando datos tipados.

Esto se puede lograr en 6 pasos:

  1. Cree un proyecto en Builder y obtenga una clave de acceso
  2. Inicialice una aplicación React Vite
  3. Use Sequence Wallet para el inicio de sesión del usuario
  4. Utilice datos tipados EIP712 para generar firmas EIP6492
  5. Implemente el contrato para verificación EIP712 y validación EIP1271
  6. Renderice la respuesta del contrato de verificación y validación

El flujo general de esta aplicación se puede ver en el siguiente diagrama de secuencia:

Consulte el código completo del demo para más información y un ejemplo de demo aquí.

1. Cree un proyecto en Builder y obtenga una clave de acceso

Primero, siga esta guía para crear un proyecto en Sequence Builder y obtener una clave de acceso para el proyecto.

2. Inicialice una aplicación React Vite

A continuación, comience inicializando un nuevo proyecto que contendrá todo el código necesario para generar firmas y respuestas de validación desde la blockchain:

pnpm create vite

Esto debería crear un proyecto en blanco al que puede empezar a agregar elementos y lógica.

3. Use Sequence Wallet para el inicio de sesión del usuario

Instale los paquetes necesarios para que el proyecto funcione:

pnpm install 0xsequence ethers

Luego, permita que un usuario inicie sesión en la red seleccionada y con la clave de acceso del proyecto obtenida en el paso 1.



import { sequence } from '0xsequence'



function App() {

    sequence.initWallet(PROJECT_ACCESS_KEY, {

        defaultNetwork: 'sepolia',

    });



    const signIn = async () => {

        const wallet = sequence.getWallet()

        const details = await wallet.connect({app: 'sequence signature validation demo'})



        if(details){

            console.log('is signed in')

            console.log(details)

        }

    }



    return (

        ...

        <button onClick={() => signIn()}>sign in</button>

        ...

    )

}

4. Utilice datos tipados EIP712 para generar firmas EIP6492

A continuación, definiremos un dato tipado personalizado en TypeScript y, usando la biblioteca de utilidades de Sequence, construiremos un tipo TypedData, donde verificaremos una estructura de mensaje con los parámetros name, wallet y message:

En este ejemplo, VERIFYING_CONTRACT_ADDRESS es el smart contract que desplegamos en sepolia, pero en el siguiente paso le mostraremos qué hace este contrato para que pueda desplegarlo usted mismo en cualquier red:

import { sequence } from '0xsequence'



interface Person {

  name: string;

  wallet: string;

  message: string;

}



const VERIFYING_CONTRACT_ADDRESS = '0xB81efF8d6700b83B24AA69ABB18Ca8f9F7A356c5'

const CHAIN_ID = 11155111



const submitSignature = () => {

    const wallet = sequence.getWallet()



    const message = 'hey' // message can be dynamic

    const person: Person = {

        name: "user", // name can be dynamic

        wallet: wallet.getAddress(),

        message: message,

    };



    const chainId = CHAIN_ID

    const typedData: sequence.utils.TypedData = {

        domain: {

            // Domain settings must match verifying contract

            name: "Sequence Signature Validation Demo",

            version: "1",

            chainId,

            verifyingContract: VERIFYING_CONTRACT_ADDRESS,

        },

        types: {

            Person: [

                { name: "name", type: "string" },

                { name: "wallet", type: "address" },

                { name: "message", type: "string" },

            ],

        },

        message: person,

        primaryType: "Person",

    };

    ...

}


Luego, firmaremos el objeto de mensaje tipado con las distintas propiedades referenciadas:

const wallet = await sequence.getWallet()

const signer = wallet.getSigner(CHAIN_ID);



const signature = await signer.signTypedData(

    typedData.domain,

    typedData.types,

    typedData.message,

    {

        chainId,

        eip6492: true, // enabling signatures for non-deployed wallet contracts

    }

);



console.log("signature", signature);

Perfecto, asocie la función a un botón y observe cómo se genera la firma después de que el usuario haga clic en el botón:

<button onClick={() => submitSignature()}>verify signature</button>

5. Despliegue el contrato para verificación EIP712 y validación EIP1271

Ahora le proporcionaremos el código fuente que puede utilizar en herramientas como Remix para desplegar un contrato, o incluso algo como Foundry para construir y desplegar con el Sequence Builder

1

Universal Signature Validator

El Universal Signature Validator puede, en teoría, desplegarse una sola vez para una red específica y compartirse entre muchas aplicaciones, lo que lo hace componible y reutilizable. Se utiliza tanto para wallets inteligentes off-chain como on-chain compatibles con EIP6492.

Puede encontrar el código fuente aquí para desplegarlo.

2

Verificador de contrato personalizado

El siguiente contrato lo explicaremos más en detalle junto con sus distintas funciones, ya que puede personalizarse para la aplicación específica. Comience con lo básico, pasando el Universal Signature Validator en el constructor en el primer paso:

import {IERC1271} from "./interfaces/IERC1271.sol";

import {IERC6492} from "./interfaces/IERC6492.sol";



import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";



struct Person { // can be customized

    string name;

    address wallet;

    string message;

}



contract EIP712Verifier is EIP712 {

    using ECDSA for bytes32;



    IERC6492 public immutable ERC6492_SIGNATURE_VALIDATOR; // the universal signature validator



    bytes32 private constant _ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492;



    // this line of code must be customized to the struct you're verifying

    bytes32 private constant _PERSON_TYPEHASH = keccak256(bytes("Person(string name,address wallet,string message)"));



    constructor(address erc6492SignatureValidator) {

        ERC6492_SIGNATURE_VALIDATOR = IERC6492(erc6492SignatureValidator);

    }

    ...

}


3

Verificar firma

A continuación, tenemos la función de verificación de firma que tanto crea el hash del mensaje como valida al firmante:

/// @dev Verifies the signature of a person.

function verifySignature(address signer, Person memory person, bytes calldata signature)

    external

    returns (bool success)

{

    bytes32 digest = personDigest(person);

    return ERC6492_SIGNATURE_VALIDATOR.isValidSig(signer, digest, signature);

}

Digest personalizado de persona

En la siguiente función, recreamos el hash de la estructura con los parámetros recibidos, los cuales pueden ampliarse para incluir más o menos parámetros de distintos tipos:

Para más información sobre cómo construir el digest, consulte la especificación EIP712.

/// @dev Returns the EIP712 hash of a person.

function personDigest(Person memory person) public view returns (bytes32 digest) {

    bytes32 structHash = keccak256(

        abi.encode(_PERSON_TYPEHASH, keccak256(bytes(person.name)), person.wallet, keccak256(bytes(person.message)))

    );

    digest = EIP712._hashTypedDataV4(structHash);

}

Validar firmante

A continuación, validamos la dirección del signer, el digest y la signature. Si se ha proporcionado una firma EIP6492, usamos el Universal Signature Validator; de lo contrario, verificamos la firma EIP1271 directamente:

/// @dev Validates the ERC1271 signature of a signer.

function validateSigner(address signer, bytes32 digest, bytes calldata signature) internal returns (bool success) {

    if (signature.length >= 32) {

        bool isCounterfactual =

            bytes32(signature[signature.length - 32:signature.length]) == _ERC6492_DETECTION_SUFFIX;

        if (isCounterfactual) {

            return ERC6492_SIGNATURE_VALIDATOR.isValidSig(signer, digest, signature);

        }

    }



    try IERC1271(signer).isValidSignature(digest, signature) returns (bytes4 magicValue) {

        return magicValue == IERC1271.isValidSignature.selector;

    } catch {}

    return false;

}

Ya está listo para desplegar ambos contratos, asegúrese de elegir su red.

6. Renderice la respuesta del contrato de verificación y validación

Pasamos las firmas y llamamos al contrato desplegado usando ethers con el PROJECT_ACCESS_KEY en los siguientes pasos:

1

Cree un provider

Cree un provider usando la clave de acceso del proyecto:

import { ethers } from 'ethers'



const CHAIN_HANDLE = 'sepolia'



const provider = new ethers.JsonRpcProvider(

    `https://nodes.sequence.app/${CHAIN_HANDLE}/${PROJECT_ACCESS_KEY}`

);
2

Inicialice un contrato de Ethers

Importe el ABI generado en el paso 5 (o cópielo del código fuente en git), incluya el provider e ingrese la dirección del contrato verificador:

import { ABI } from "./abi";



const contract = new ethers.Contract(

    VERIFYING_CONTRACT_ADDRESS,

    ABI,

    provider

);
3

Llamada estática a la función de verificación de firma

Al realizar una llamada estática a la función, simulamos la transacción sin enviarla en cadena. Esto devuelve un resultado que especifica si la validación fue verdadera o falsa:

const address = await wallet.getAddress()



const person: Person = {

    name: "user",

    wallet: address,

    message: message,

}



const signature = await signer.signTypedData(

    typedData.domain,

    typedData.types,

    typedData.message,

    {

        chainId,

        eip6492: true,

    }

);



const result = await contract.verifySignature.staticCall(

    address,

    person,

    signature

);



console.log(`Signature is ${result ? "valid" : "invalid"}`);

return result;

Conclusión

Ahora que tenemos estructuras de mensajes enviadas a la blockchain y mensajes verificados con sus entradas, podemos ampliar la aplicación a muchos casos de uso que aseguren que los usuarios firman la información correcta (por ejemplo, permitir gastar ERC20, realizar ofertas fuera de cadena, códigos QR que contienen firmas aprobadas para minteo, etc.).