Calcular la dirección de la cadena de clave pública en Solidity

TL; DR: necesito calcular la dirección de una cadena de clave pública determinada en Solidity.

Actualmente estoy tratando de descubrir cómo calcular la dirección a partir de una clave pública, dada como una cadena (o bytes) en solidez. Estoy fallando hasta ahora para hacerlo bien. Como hay varios pasos involucrados y Solidity no es el más fácil de depurar, quería preguntar aquí para ver si hay algún error en mi proceso de pensamiento.

Así que aquí va:

Tengo la clave dada como bytes o como una representación de cadena. Bytes me parece bastante sencillo, pero cuando se habla de la representación de cadenas, no estoy muy seguro de la forma correcta de codificar/decodificar. De todos modos, cuando persiste una clave, la representación de cadenas me parece más habitual, por lo que este es el enfoque principal aquí. Para fines de prueba, generé un par de claves en Java/Web3j como este:

ECKeyPair keyPair = Keys.createEcKeyPair();
Credentials c = Credentials.create(keyPair);

System.out.println("Private key: " + keyPair.getPrivateKey().toString(16));
System.out.println("Public key: " + keyPair.getPublicKey().toString(16));
System.out.println("Address: " + credentials.getAddress());
            

Esto ahora me genera un par de claves aleatorias y genera claves con formato hexadecimal y la dirección asociada. Usemos el siguiente ejemplo:

Public key: 3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb
Address: 0x622cf04ee8659bc45d76def393077ddcc5396761

Ahora, para calcular la dirección de una clave dada como una cadena, tendré que codificar la clave y usar los últimos 20 bytes ( https://en.wikipedia.org/wiki/Ethereum#Addresses ). Esto también está en línea con el libro Ethereum: https://github.com/ethereumbook/ethereumbook/blob/develop/04keys-addresses.asciidoc#ethereum-addresses

En Solidity, el hash a través de keccack256 requiere una bytesentrada, por lo que convierto mi cadena en bytes (aquí es donde usaría la entrada de bytes si la clave se proporcionara como tal):

bytes memory bytesKey;
bytesKey = abi.encodePacked(strKey);

El resultado de eso (por ejemplo, si se devuelve como un parámetro de retorno de una función de Solidez) para la clave de ejemplo anterior es

bytes: 0x3362383862353338646666376462383133623663386265366266636538316636646439643832303231336665393231316539663561363331633336306337646462623236363930616534306561633632653062356161663264386135633432383765336333383366633163303039313663653132653335346531656231326562

Ahora, tendré que calcular la dirección real a partir de eso. El primer paso aquí sería calcular el hash a través de keccack256(). Haciendo esto me da con la tecla de ejemplo

bytes32: 0xb79316bedc9b38a71ead3f08f90433a4f4ae7a2ba5f71ef4fc85827c884f1b5d

La dirección de este debe ser los 20 bytes menos significativos, en otras palabras, los 40 caracteres más a la derecha en este. El problema es que esos no equivalen a la dirección calculada por Web3j en primer lugar. Pero vayamos paso a paso.

Solidity address()espera una byte20entrada. Pero si lo convertiría explícitamente en una bytes20variable como bytes20 x = bytes20(keyHash);, obtendría los 20 bytes más significativos. Encontré una respuesta en Obtener dirección de clave pública en Solidity que me permite leer correctamente los últimos 20 bytes (40 caracteres) del hash:

assembly {
 mstore(0, keyHash)
 addr := mload(0)
} 

Funciona bien como se anuncia, pero aún así el resultado ( 0xf90433A4F4aE7A2ba5f71ef4Fc85827c884F1b5d) no coincide con la dirección esperada0x622cf04ee8659bc45d76def393077ddcc5396761

Mi suposición es que en algún lugar se usó incorrectamente una conversión o codificación. De todos modos, los documentos de Solidity y los recursos de Ethereum en general, no puedo encontrar ninguna explicación explícita de todo esto. Puede alguien ayudarme con esto?

Para fines de prueba, aquí está el código completo de Solidity. Tenga en cuenta que en el código real, esa cadena clave se pasará como un parámetro y no se codificará como se hace aquí:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;

contract KeyTest {
    function test() public returns (address) {    
        string memory key = "3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb";
        string memory addrOriginal = "0x622cf04ee8659bc45d76def393077ddcc5396761";
        
        // convert string to bytes
        bytes memory bytesKey = abi.encodePacked(key);
        
        //results in bytesKey: 0x3362383862353338646666376462383133623663386265366266636538316636646439643832303231336665393231316539663561363331633336306337646462623236363930616534306561633632653062356161663264386135633432383765336333383366633163303039313663653132653335346531656231326562
        
        bytes32 keyHash = keccak256(bytesKey);
        //bytes32: 0xb79316bedc9b38a71ead3f08f90433a4f4ae7a2ba5f71ef4fc85827c884f1b5d
        
        address addr;
        
        address addr;
        assembly {
            mstore(0, keyHash)
            addr := mload(0)
        }
        // addr: 0xf90433A4F4aE7A2ba5f71ef4Fc85827c884F1b5d
        
        return addr;
    }
}

Respuestas (1)

El problema es que abi.encodePacked(key)interpreta la clave como un valor de cadena, no como un valor hexadecimal. Si bien hay formas de convertir una cadena hexadecimal en bytes en Solidity , es mucho más fácil declararla como bytes en primer lugar:

bytes memory publicKey = hex"3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb";

Luego, simplemente puede enviar el resultado a una dirección para obtener la dirección del hash Keccak-256 completo:

function test() public pure returns (address) {
  bytes memory publicKey = hex"3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb";
  bytes32 hash = keccak256(publicKey);
    
  return address(uint160(uint256(hash)));
}

Llamar a esto da 0x622CF04ee8659bC45d76deF393077Ddcc5396761como se esperaba.

¿Hay alguna razón para usar ensamblaje aquí? why not just -> address(uint160(uint256(keccak256(bytes(hex"3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb")))));
Copié eso de la pregunta original, pero tienes razón en que eso es un poco más simple (y un poco más eficiente en el consumo de combustible). He actualizado mi respuesta.
Hola, gracias por la respuesta, eso me aclara mucho las cosas. De todos modos, una pequeña pregunta de seguimiento: como en el ejemplo, la cadena clave se codificó para fines de prueba, en el código real, la pasaré como un parámetro (memoria de cadena). hex"abcd..."entonces no se puede usar. Desafortunadamente, ninguna función hex()parece existir. ¿Uno tiene que pasar por la extensa transcodificación "manual" en la publicación vinculada por usted o hay otra forma?
¿ No puedes simplemente pasar la clave pública como bytes memory? No estoy muy familiarizado con Web3j, pero supongo que tiene una forma de hacerlo, y convertir una cadena hexadecimal en bytes debería ser mucho más fácil (y más eficiente) en Java que en Solidity.
Esa es la otra opción. De todos modos, necesito una forma de pasar una clave con formato de cadena
@Xenonite pregunta eso en otra pregunta, estoy seguro de que puedes convertirlo con Web3j, y como dijo, es más fácil y no cuesta gasolina usando Java.