Obtenga la clave pública de cualquier cuenta de ethereum

¿Puedo de alguna manera obtener la clave pública de una cuenta de ethereum sabiendo solo la dirección de ethereum correspondiente (p. ej 0x54dbb737eac5007103e729e9ab7ce64a6850a310.)?

Respuestas (5)

Puede hacerlo si y solo si se ha enviado una transacción desde la cuenta. Cuando envía un tx, firma la transacción e incluye estos v ry svalores. Los analiza desde el tx firmado y luego pasa estos valores v ry sel hash de la transacción nuevamente a una función y escupirá la clave pública. Así es como se obtiene la dirección de origen de una transacción.

Puede hacerlo usted mismo usando una herramienta como ethereumjs-utils :

/**
 * ECDSA public key recovery from signature
 * @param {Buffer} msgHash
 * @param {Number} v
 * @param {Buffer} r
 * @param {Buffer} s
 * @return {Buffer} publicKey
 */
exports.ecrecover = function (msgHash, v, r, s) {
  var signature = Buffer.concat([exports.setLength(r, 32), exports.setLength(s, 32)], 64)
  var recovery = v - 27
  if (recovery !== 0 && recovery !== 1) {
    throw new Error('Invalid signature v value')
  }
  var senderPubKey = secp256k1.recover(msgHash, signature, recovery)
  return secp256k1.publicKeyConvert(senderPubKey, false).slice(1)
}

Como otro escenario del mundo real, ethereumjs-tx usa esta función para verificar la firma:

/**
* Determines if the signature is valid
* @return {Boolean}
*/
verifySignature () {
  const msgHash = this.hash(false)
  // All transaction signatures whose s-value is greater than secp256k1n/2 are considered invalid.
  if (this._homestead && new BN(this.s).cmp(N_DIV_2) === 1) {
    return false
  }

  try {
    let v = ethUtil.bufferToInt(this.v)
    if (this._chainId > 0) {
      v -= this._chainId * 2 + 8
    }
    this._senderPubKey = ethUtil.ecrecover(msgHash, v, this.r, this.s)
  } catch (e) {
    return false
  }

  return !!this._senderPubKey
}

Para obtener más información sobre v ry s:

v, r y s son parámetros que se pueden analizar a partir de la firma. Aquí hay un buen ejemplo de la biblioteca utils de ethereumjs:

 var sig = secp256k1.sign(msgHash, privateKey)
  var ret = {}
  ret.r = sig.signature.slice(0, 32)
  ret.s = sig.signature.slice(32, 64)
  ret.v = sig.recovery + 27

Tenga en cuenta cómo puede analizar cada valor de una firma dada.

fuente

Muy útil. Tuve que ver por qué secp256k1.publicKeyConvertse necesita una llamada "extra" y el motivo parece ser obtener la clave pública descomprimida, ya que secp256k1.recoverdevuelve una clave pública comprimida.
¿Qué representan los valores s, r, v?
¿Qué es el msgHash? ¿Es ese el hash del mensaje enviado como parte de la transacción o el hash real de la transacción en sí?
¿Cómo va a analizar v, r y si no tiene la clave privada del usuario final?

Ahora es posible recuperar la clave pública de la transacción de Ethereum sin ningún tipo de codificación:

  1. Abra la transacción en Etherscan.io
  2. Haga clic en los puntos suspensivos verticales en la esquina superior derecha
  3. Haga clic en "Obtener Raw Tx Hex" en el menú emergente
  4. Verá la transacción sin procesar en hexadecimal, cópiela
  5. Abra la herramienta "Recuperar dirección" de ABDK Toolkit
  6. Seleccione el botón de opción "Transacción"
  7. Pegue la transacción sin procesar en el campo "Mensaje"
  8. Vea la clave pública en el arco de texto a continuación
Esto funciona extremadamente bien. Sin embargo, no pude encontrar el código fuente de esto en ninguna parte, aunque dicen que es de código abierto.
Lo mejor que pude hacer es crear un archivo WARC para uso sin conexión aquí .
¿Cómo ayuda esto simplemente sabiendo la dirección?

No creo que esto sea posible, ya que pierde información al pasar de la clave pública a la dirección:

  1. Comience con la clave pública (64 bytes)
  2. Tome el hash Keccak-256 de la clave pública. Ahora debería tener una cadena de 32 bytes. (nota: SHA3-256 eventualmente se convirtió en el estándar, pero Ethereum usa Keccak)
  3. Tome los últimos 20 bytes de esta clave pública (Keccak-256). O, en otras palabras, suelte los primeros 12 bytes . Estos 20 bytes son la dirección, o 40 caracteres. Cuando tiene el prefijo 0x, se convierte en 42 caracteres.

¿Cómo se generan las direcciones ethereum?

Pensé que todas las transacciones desde una dirección de ethereum se firman con la clave privada de la cuenta y se validan en cadena con la clave pública de la cuenta. Si ese es el caso, las claves públicas de todas las cuentas deberían estar disponibles para todos los nodos de una red. ¿Me equivoco?
@EdwardRuchevits La dirección en la pregunta no ha firmado una transacción, por lo que no es factible obtener la clave pública. Cuando hay una firma, esto puede ayudar a crypto.stackexchange.com/questions/18105/… y stackoverflow.com/questions/19665491/… Tal vez algunas personas también puedan agregar excelentes respuestas aquí.
Una buena propiedad de las direcciones es que, dado que la clave pública no se conoce hasta que se usa la cuenta, la cuenta está a salvo de ataques cuánticos hasta que se gasta de
@TjadenHess ¿Son los ataques cuánticos una cosa en la actualidad?

Usando la sugerencia de @tayvano, puede hacerlo de la siguiente manera:

  1. Vaya a etherscan y verifique si hay transacciones salientes: https://etherscan.io/address/0x54dbb737eac5007103e729e9ab7ce64a6850a310
  2. Obtener hash de uno de ellos, por ejemplo0xa8206c5fcfb6a2527fb8540ab543b4701f4c86d1c21862ad89fa220c84bad260
  3. usar ipython con web3
In [1]: import web3
w3 = web3.Web3(web3.HTTPProvider('https://geth.golem.network:55555'))
tx = w3.eth.getTransaction(0xa8206c5fcfb6a2527fb8540ab543b4701f4c86d1c21862ad89fa220c84bad260)
tx.hash
Out[1]: HexBytes('0xa8206c5fcfb6a2527fb8540ab543b4701f4c86d1c21862ad89fa220c84bad260')

In [2]: from eth_account.internal.signing import extract_chain_id, to_standard_v
s = w3.eth.account._keys.Signature(vrs=(
    to_standard_v(extract_chain_id(tx.v)[1]),
    w3.toInt(tx.r),
    w3.toInt(tx.s)
))

from eth_account.internal.transactions import ALLOWED_TRANSACTION_KEYS
tt = {k:tx[k] for k in ALLOWED_TRANSACTION_KEYS - {'chainId', 'data'}}
tt['data']=tx.input
tt['chainId']=extract_chain_id(tx.v)[0]

from eth_account.internal.transactions import serializable_unsigned_transaction_from_dict
ut = serializable_unsigned_transaction_from_dict(tt)
s.recover_public_key_from_msg_hash(ut.hash())
Out[2]: '0x9678ad0aa2fbd7f212239e21ed1472e84ca558fecf70a54bbf7901d89c306191c52e7f10012960085ecdbbeeb22e63a8e86b58f788990b4db53cdf4e0a55ac1e'

In [3]: s.recover_public_key_from_msg_hash(ut.hash()).to_checksum_address()
Out[3]: '0x54Dbb737EaC5007103E729E9aB7ce64a6850a310'

In [4]: t['from']
Out[4]: '0x54Dbb737EaC5007103E729E9aB7ce64a6850a310'
Esto no funciona para las transacciones de implementación de contratos. Y para la última versión, debe ajustar el código parafrom eth_account._utils.signing import extract_chain_id, to_standard_v, serializable_unsigned_transaction_from_dict from eth_account._utils.transactions import ALLOWED_TRANSACTION_KEYS

Solución en Java. Tal vez se puede hacer más simple, pero funciona.

Las clases de utilidad provienen de web3j.crypto.

    BigInteger v = new BigInteger("26", 16);
    BigInteger r = new BigInteger("5fd883bb01a10915ebc06621b925bd6d624cb6768976b73c0d468b31f657d15b", 16);
    BigInteger s = new BigInteger("121d855c539a23aadf6f06ac21165db1ad5efd261842e82a719c9863ca4ac04c", 16);
    BigInteger chainId = new BigInteger("1", 16);
    v = v.subtract(chainId.multiply(BigInteger.valueOf(2)).add(BigInteger.valueOf(8)));
    Sign.SignatureData signatureData = new Sign.SignatureData(v.toByteArray(), r.toByteArray(), s.toByteArray());
    byte[] raw = DatatypeConverter.parseHexBinary("f86b0b85250523760082520894eafaf9bb8f35235d0df61275e86fd65d9ef2c3f9870aaa0065c66b8b8026a05fd883bb01a10915ebc06621b925bd6d624cb6768976b73c0d468b31f657d15ba0121d855c539a23aadf6f06ac21165db1ad5efd261842e82a719c9863ca4ac04c");

    RawTransaction decoded = TransactionDecoder.decode(DatatypeConverter.printHexBinary(raw));
    byte[] encoded = TransactionEncoder.encode(decoded, chainId.longValue());
    byte[] rawTxHash = Hash.sha3(encoded);

    System.out.println("Raw tx hash:                    " + DatatypeConverter.printHexBinary(rawTxHash));
    System.out.println("Pub key from raw tx hash :      " + signedMessageHashToKey(rawTxHash, signatureData).toString(16));