Ethereum: Obtener la clave pública de una transacción

Cualquier transacción en Ethereum se firma con la clave privada de quien envía esa transacción, que está identificado por su dirección (address), que a su vez, se obtiene a partir de la clave pública. La validación de una transacción requiere comprobar que la firma es correcta y que quien firma es realmente quien dice ser.

Los datos de una transacción incluyen la información de la dirección de origen (address) y la firma mediante tres valores r,s y v. La clave pública, que es necesaria para verificar la firma (y con ella derivar la dirección address y comprobarla también) no aparece de manera explícita en la transacción, pero puede obtenerse.

La mayoría de frameworks tiene funciones para ello (p.ej. ecrecover, páginas tipo ABDK Toolkit, etc.), y existen en la web multitud de artículos al respecto (basta buscar textos del tipo «get public key from ethereum transaction» o similares).

Sin embargo, en general porque están desactualizados, en muchos no se contempla el EIP-1559 (https://eips.ethereum.org/EIPS/eip-1559), que introdujo cambios en la gestión de las comisiones (fees) y en formato de las transacciones y, como consecuencia en los datos que se firman.

A continuación se muestra una prueba simple para obtener la clave pública de una transacción de Ethereum, usando una de las muchas librerías javascript que existen para tratar con esta blockchain (https://github.com/ethers-io/ethers.js) (en su última versión) y las siguientes respuestas en la web, que hay que agradecer:

https://ethereum.stackexchange.com/questions/78815/ethers-js-recover-public-key-from-contract-deployment-via-v-r-s-values

https://gist.github.com/chrsengel/2b29809b8f7281b8f10bbe041c1b5e00 , en donde se muestra el código javascript y la adaptación al nuevo tipo de transacciones que se introdujeron con el EIP-1559

Se trata de usar ese código y hacerlo funcional de manera muy sencilla, mediante un archivo html (que puede ejecutarse directamente como archivo en el navegador, o bien levantando un sencillo servidor http, p.ej. con python)

El framework ethers-js necesita conectarse a un nodo de Ethereum, pudiendo ser uno de la red Infura, o nuestro propio nodo local geth, pero en este caso, hay que ejecutarlo añadiendo la opción cors para evitar errores por ello, con este comando:

geth  --syncmode "light"  --http --http.api debug,eth,web3,personal,net,admin -http.corsdomain '*'

El código es muy sencillo, partiendo del hash de la transacción, escrita directamente en el código, presenta en pantalla los valores obtenidos de la transacción, y luego con las funciones de ethers-js, compone la transacción sin firmar, y calcula la clave pública y la dirección.

Caso de una transacción de tipo transferencia de moneda, el tipo de la transacción de valor «2» se corresponde con el definido por el EIP-1559:

Caso de una transacción de creación de un contrato, donde se aprecia la dirección del contrato creado, y que no existe dirección de destino de la transacción.

El código fuente del archivo html (hay que usar la versión 5.4 de ethers-js, las anteriores no son compatibles con el nuevo tipo de transacciones, y si se usa la red Infura hay que poner el API KEY):

<html>
<head>
</head>
<body>
<script src="https://cdn.ethers.io/lib/ethers-5.4.umd.min.js" type="text/javascript">
</script>

<script>

//const txHash = '0x8997aabd3c511bf3bcfdad9e335527df62bf33043caee671684f5791e437cc37'; // tipo 2
const txHash = '0xd3ccf9466b92e907b17b9c6712ef9d91c1794e7b0f319be5c65bcda1d192c0b8'; // creacción de un contrato

getPubKey = async () => {
    //const myProvider = new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/PONER_API_KEY")
    const myProvider = new ethers.providers.JsonRpcProvider("http://127.0.0.1:8545") // geth : permitir cors 

    const tx = await myProvider.getTransaction(txHash)
    const expandedSig = {
        r: tx.r,
        s: tx.s,
        v: tx.v
    }

    document.write("<b>Valores extraidos de la transacci&oacuten</b>" + "<br><hr>")
    console.log("TX: ",tx);

    for (var property in tx) {
      if (property!='wait') {document.write(property + ': ' + tx[property]+' <br>')}
    }
   

    const signature = ethers.utils.joinSignature(expandedSig)
     let txData;
        switch (tx.type) {
        case 0:
            txData = {
                gasPrice: tx.gasPrice,
                gasLimit: tx.gasLimit,
                value: tx.value,
                nonce: tx.nonce,
                data: tx.data,
                chainId: tx.chainId,
                to: tx.to
            };
            break;
        case 2: //EIP-1559
            txData = {
                gasLimit: tx.gasLimit,
                value: tx.value,
                nonce: tx.nonce,
                data: tx.data,
                chainId: tx.chainId,
                to: tx.to,
                type: 2,
                maxFeePerGas: tx.maxFeePerGas,
                maxPriorityFeePerGas: tx.maxPriorityFeePerGas
            }
            break;
        default:
            throw "Unsupported tx type";
        }
    // 
    const rsTx = await ethers.utils.resolveProperties(txData)
    const raw = ethers.utils.serializeTransaction(rsTx) // returns RLP encoded tx (unsigned)
    const msgHash = ethers.utils.keccak256(raw) // as specified by ECDSA
    const msgBytes = ethers.utils.arrayify(msgHash) // create binary hash
    const recoveredPubKey = ethers.utils.recoverPublicKey(msgBytes, signature)
    const recoveredAddress = ethers.utils.recoverAddress(msgBytes, signature)

    document.write("<br><b>Valores obtenidos de las operaciones crypto con la transacci&oacuten</b>"  + "<br><hr>");

    document.write("From Address: " + recoveredAddress + "<br>");
    document.write("Public Key: " + recoveredPubKey + "<br>");
    check = ( recoveredAddress === tx.from );
    document.write("<br>Correct address: "+ check+ "<br>");

    }

getPubKey() //ejecutar la función
</script>
</body>
</html>

Leave a Reply

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.