Вычислить адрес строки открытого ключа в Solidity

TL;DR: мне нужно вычислить адрес из заданной строки открытого ключа в Solidity.

В настоящее время я пытаюсь выяснить, как вычислить адрес из открытого ключа, заданного в виде строки (или байтов) в твердости. Я пока не могу понять это правильно. Поскольку требуется несколько шагов, а отладка Solidity не самая простая, я хотел спросить здесь, есть ли какие-либо ошибки в моем мыслительном процессе.

Итак, вот оно:

У меня есть ключ либо в виде байтов, либо в виде строкового представления. Байты кажутся мне довольно простыми, но, говоря о строковом представлении, я не слишком уверен в правильном способе кодирования/декодирования. Во всяком случае, при сохранении ключа представление String кажется мне более привычным, поэтому здесь основное внимание уделяется этому. В целях тестирования я создал пару ключей в Java/Web3j следующим образом:

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());
            

Теперь это генерирует мне случайную пару ключей и выводит ключи в шестнадцатеричном формате и соответствующий адрес. Давайте воспользуемся следующим примером:

Public key: 3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb
Address: 0x622cf04ee8659bc45d76def393077ddcc5396761

Теперь, чтобы вычислить адрес ключа, заданного в виде строки, мне придется хэшировать ключ и использовать его последние 20 байтов ( https://en.wikipedia.org/wiki/Ethereum#Addresses ). Это также соответствует книге Ethereum: https://github.com/ethereumbook/ethereumbook/blob/develop/04keys-addresses.asciidoc#ethereum-addresses.

В Solidity хеширование через keccack256 требует bytesввода, поэтому я конвертирую свою строку в байты (здесь я бы просто использовал ввод байтов, если бы ключ был предоставлен как один):

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

Результатом этого (например, если он возвращается как возвращаемый параметр из функции Solidity) для приведенного выше примера ключа является

bytes: 0x3362383862353338646666376462383133623663386265366266636538316636646439643832303231336665393231316539663561363331633336306337646462623236363930616534306561633632653062356161663264386135633432383765336333383366633163303039313663653132653335346531656231326562

Теперь мне нужно вычислить фактический адрес из этого. Первым шагом здесь будет вычисление хэша через keccack256(). Выполнение этого дает мне пример ключа

bytes32: 0xb79316bedc9b38a71ead3f08f90433a4f4ae7a2ba5f71ef4fc85827c884f1b5d

Адрес этого должен быть 20 младшими байтами, другими словами, 40 крайними правыми символами в этом. Проблема в том, что они не равны адресу, вычисленному Web3j. Но давайте шаг за шагом.

Solidity address()ожидает byte20ответа. Но если бы я явно привел его в bytes20переменную типа bytes20 x = bytes20(keyHash);, я бы получил 20 самых значащих байтов. Я нашел ответ в разделе «Получить адрес из открытого ключа в Solidity» , который позволяет мне правильно прочитать последние 20 байт (40 символов) хэша:

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

Работает нормально, как рекламируется, но результат ( 0xf90433A4F4aE7A2ba5f71ef4Fc85827c884F1b5d) не соответствует ожидаемому адресу.0x622cf04ee8659bc45d76def393077ddcc5396761

Я предполагаю, что где-то неправильно использовалось преобразование или кодирование. Во всяком случае, в документации Solidity и ресурсах Ethereum в целом я не могу найти явного объяснения всему этому. Кто-нибудь может мне с этим помочь?

В целях тестирования, вот весь код Solidity. Обратите внимание, что в реальном коде эта ключевая строка будет передаваться как параметр, а не жестко закодирована, как это сделано здесь:

// 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;
    }
}

Ответы (1)

Проблема в том, что abi.encodePacked(key)ключ интерпретируется как строковое значение, а не как шестнадцатеричное значение. Хотя в Solidity есть способы преобразовать шестнадцатеричную строку в байты , в первую очередь гораздо проще объявить ее как байты:

bytes memory publicKey = hex"3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb";

Затем вы можете просто привести результат к адресу, чтобы получить адрес из полного хэша Keccak-256:

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

Вызов этого приводит к 0x622CF04ee8659bC45d76deF393077Ddcc5396761ожидаемому результату.

Есть ли причина использовать сборку здесь? why not just -> address(uint160(uint256(keccak256(bytes(hex"3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb")))));
Я скопировал это из исходного вопроса, но вы правы в том, что это немного проще (и немного более экономично). Я обновил свой ответ.
Привет, спасибо за ответ, который многое проясняет для меня. Во всяком случае, один небольшой дополнительный вопрос: как и в примере, ключевая строка была жестко запрограммирована для целей тестирования, в реальном коде я передам ее как параметр (строковая память). hex"abcd..."тогда нельзя использовать. К сожалению, никакой функции, hex()кажется, не существует. Нужно ли проходить обширное «ручное» перекодирование в сообщении, на которое вы ссылаетесь, или есть другой способ?
Разве вы не можете просто передать открытый ключ как bytes memory? Я не слишком хорошо знаком с Web3j, но я предполагаю, что у него есть способ сделать это, и преобразование шестнадцатеричной строки в байты должно быть намного проще (и эффективнее) в Java, чем в Solidity.
Это другой вариант. Во всяком случае, мне нужен способ передать ключ в формате строки
@Xenonite спросите об этом в другом вопросе, я уверен, что вы можете преобразовать его с помощью Web3j, и, как он сказал, это проще и не требует затрат газа с использованием java.
Хорошо, сделал это: ethereum.stackexchange.com/questions/102044/…