Могу ли я каким-то образом получить открытый ключ учетной записи ethereum, зная только соответствующий адрес ethereum (например, 0x54dbb737eac5007103e729e9ab7ce64a6850a310
)?
Вы можете, если и только если транзакция была отправлена со счета. Когда вы отправляете tx, вы подписываете транзакцию, и она включает в себя эти v
r
и s
значения. Вы анализируете их из подписанного tx, а затем передаете их v
r
, s
значения и хэш транзакции обратно в функцию, и она выдает открытый ключ. На самом деле именно так вы получаете адрес отправителя транзакции.
Вы можете сделать это самостоятельно, используя такой инструмент, как 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)
}
В качестве другого реального сценария ethereumjs-tx использует эту функцию для проверки подписи:
/**
* 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
}
Для получения дополнительной информации о v
r
и s
:
v, r и s — это параметры, которые можно проанализировать из подписи. Вот хороший пример из библиотеки утилит 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
Обратите внимание, как вы можете анализировать каждое значение из данной подписи.
Теперь можно восстановить открытый ключ из транзакции Ethereum без какого-либо кодирования:
Я не думаю, что это возможно, так как вы теряете информацию при переходе от открытого ключа к адресу:
- Начните с открытого ключа (64 байта)
- Возьмите хэш открытого ключа Keccak-256. Теперь у вас должна быть строка размером 32 байта. (примечание: SHA3-256 со временем стал стандартом, но Ethereum использует Keccak)
- Возьмите последние 20 байт этого открытого ключа (Keccak-256). Или, другими словами, отбросить первые 12 байт . Эти 20 байт являются адресом или 40 символами. С префиксом 0x он становится длиной 42 символа.
Используя подсказку @tayvano, вы можете сделать это следующим образом:
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
Решение на Java. Возможно, это можно сделать проще, но это работает.
Вспомогательные классы взяты из 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));
эт
secp256k1.publicKeyConvert
нужен «дополнительный» вызов, и причина, по-видимому, в том, чтобы получить распакованный открытый ключ, посколькуsecp256k1.recover
он возвращает сжатый открытый ключ.Джон
Технический инженер Нью-Йорка
Юлиан