¿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
.)?
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
r
y s
valores. Los analiza desde el tx firmado y luego pasa estos valores v
r
y s
el 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
r
y 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.
Ahora es posible recuperar la clave pública de la transacción de Ethereum sin ningún tipo de codificación:
No creo que esto sea posible, ya que pierde información al pasar de la clave pública a la dirección:
- Comience con la clave pública (64 bytes)
- 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)
- 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.
Usando la sugerencia de @tayvano, puede hacerlo de la siguiente manera:
0xa8206c5fcfb6a2527fb8540ab543b4701f4c86d1c21862ad89fa220c84bad260
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'
from 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));
ética
secp256k1.publicKeyConvert
se necesita una llamada "extra" y el motivo parece ser obtener la clave pública descomprimida, ya quesecp256k1.recover
devuelve una clave pública comprimida.John
Ingeniero de tecnología de la ciudad de Nueva York
julian