Recuperar la clave pública de una dirección para verificar las firmas fuera de la cadena

Tengo una pregunta sobre el uso de claves en Ethereum para firmar solicitudes/datos FUERA de Ethereum.

Digamos que tengo un Ethereum EOA regular con una dirección y un par de claves. Ahora quiero que se transfiera una parte de la información a un tercero (fuera de la cadena) y permitir que este tercero verifique que esta información provino de mí, es decir, el propietario del EOA en cuestión. Enfoque: firmar digitalmente la información con mi clave privada asociada a mi EOA.

Ahora la pregunta es cómo el tercero está verificando la firma. Lo que tiene es la firma, los datos y (supongamos que especificamos esto) la dirección de mi EOA.

Para permitirle verificar la firma, necesitará la clave pública de mi EOA. Para hacerle llegar esto, veo dos opciones:

  1. DALE la clave pública directamente, por ejemplo, usando un registro ERC1056 donde almaceno la clave pública para cada dirección. Ahora, se me pedirá que verifique en cadena (es decir, en Solidity) que la clave pública establecida como atributo coincida con la dirección (es decir, que los primeros 20 bytes del hash de la clave pública sean los mismos). Me parece bastante costoso en términos de gas. De todos modos Primera pregunta : ¿Cómo calcular la dirección de una clave pública (cadena) en Solidity para tal verificación?
  2. Obtenga la clave pública fuera de la cadena (en el lado del receptor) de la firma. Leí que esto sería posible, pero no estoy seguro de cómo hacerlo exactamente. Luego, el destinatario calcularía la clave pública, verificaría la firma y verificaría que la firma realmente fue creada por el EOA (dirección) de la que dice ser. Por lo tanto, mi segunda pregunta : ¿Cómo hacer esto en Java? ¿Hay una biblioteca para eso?

¿Hay una tercera opción, incluso mejor?

Respuestas (2)

Primero, no es posible derivar la clave pública solo de una dirección. La dirección es parte del hash Keccak256 de la clave pública, y las funciones hash son funciones unidireccionales. Puede calcular una dirección a partir de una clave pública tomando los últimos 40 bytes del hash Keccak256.

Sin embargo, no necesita la clave pública para verificar una firma. Suponiendo que está utilizando firmas ECDSA (estándar en Ethereum), puede recuperar la clave pública de una firma y un mensaje. Si firma un mensaje usando MyCrypto, por ejemplo, el "mensaje firmado" se ve así:

{
  "address": "0xa6ad0945cd3c5539d92d49b140842a0673e17041",
  "msg": "Hello, world!",
  "sig": "0x1e15325a942ae03788b63902ccc4703a65993dc9c692cf14d2bf16d62407da6824f045ebbbc0eb8423934a87c95872703fcec742ea03ded4d88aa5e219d9494c1c",
  "version": "2"
}

Luego puede calcular la clave pública usando la función de recuperación de ECDSA ( ecrecoveren Solidity), calculando la clave pública del mensaje y la firma, codificando la clave pública usando Keccak256 y tomando los últimos 40 bytes para obtener la dirección. La firma es válida si la dirección recuperada coincide con la proporcionada en el mensaje firmado.

Debe incluir alguna información específica en el mensaje (por ejemplo, "mensaje firmado por la persona en la fecha"), para garantizar que el mensaje fue realmente firmado por la persona que afirmó haberlo firmado (y para evitar posibles ataques de repetición).

Usando Java, puede usar web3j's Sign.recoverFromSignature. Desafortunadamente, parece que esto no está documentado, pero el código fuente debería ayudarlo a comenzar.

Escribí un artículo detallado sobre las firmas ECDSA en Ethereum que puede encontrar aquí , si está interesado.

Esta biblioteca lo ayuda a recuperar la clave pública del mensaje firmado ECDSA https://github.com/0xcyphered/secp256k1-solidity

Ejemplo:

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
import "@0xcyphered/secp256k1-solidity/contracts/SECP256K1.sol";
contract Example {
    function recoverPersonalSignPublicKey(
        bytes32 message,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public pure returns (bytes memory) {
        string memory header = '\x19Ethereum Signed Message:\n32';
        bytes32 _message = keccak256(abi.encodePacked(header, message));
        (uint256 x, uint256 y) = SECP256K1.recover(uint256(_message), v - 27, uint256(r), uint256(s));
        return abi.encodePacked(x, y);
    }
}