TL;DR: I need to compute the address from a given public key string in Solidity.
I am currently trying to figure out how to compute the address from a public key, given as a string (or bytes) in solidity. I am failing so far to get it right. As there are several steps involved and Solidity isn't the easiest to debug, I wanted to ask here to see if there are any mistakes in my thought process.
So here it goes:
I have the key either given as bytes or as a string representation. Bytes seems pretty straightforward to me, but when speaking about the string representation, I am not all too sure about the correct way to encode/decode. Anyhow, when persisting a key, String representation seems more usual for me, so this is the main focus here. For testing purposes, I generated a keypair in Java/Web3j like this:
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());
This now generates me a random keypair and outputs hex-formatted keys and the associated address. Let's use the following example:
Public key: 3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb
Address: 0x622cf04ee8659bc45d76def393077ddcc5396761
Now, for me to compute the address of a key given as a string, I will have to hash the key and use the last 20 bytes of it (https://en.wikipedia.org/wiki/Ethereum#Addresses). This is also in line with the Ethereum-book: https://github.com/ethereumbook/ethereumbook/blob/develop/04keys-addresses.asciidoc#ethereum-addresses
In Solidity, hashing via keccack256 requires a bytes
input, so I convert my string into bytes (this is where I would just use the bytes input if the key was provided as one):
bytes memory bytesKey;
bytesKey = abi.encodePacked(strKey);
The result of that (e.g. if returned as a return parameter from a Solidity function) for the example key above is
bytes: 0x3362383862353338646666376462383133623663386265366266636538316636646439643832303231336665393231316539663561363331633336306337646462623236363930616534306561633632653062356161663264386135633432383765336333383366633163303039313663653132653335346531656231326562
Now, I'll have to compute the actual address from that. The first step here would be computing the hash via keccack256()
. Doing this gives me with the example key
bytes32: 0xb79316bedc9b38a71ead3f08f90433a4f4ae7a2ba5f71ef4fc85827c884f1b5d
The address of this should be the 20 least significant bytes, in other words the 40 right-most characters in this. Problem is, those don't equal the address computed by Web3j in the first place. But let's go step by step.
Solidity's address()
expects a byte20
input. But if I'd explicitly cast it into a bytes20
variable like bytes20 x = bytes20(keyHash);
, I'll get the 20 most significant bytes. I found an answer in Get address from public key in Solidity that allows me to correctly read the last 20 bytes (40 chars) of the hash:
assembly {
mstore(0, keyHash)
addr := mload(0)
}
Works fine as advertised, but still the result (0xf90433A4F4aE7A2ba5f71ef4Fc85827c884F1b5d
) does not match the expected address 0x622cf04ee8659bc45d76def393077ddcc5396761
My assumption is that somewhere a conversion or encoding was used wrongly. Anyhow, I the Solidity docs and the Ethereum resources in general, I can't find any explicit explanation of all this. Can anyone help me with this?
For testing purposes, here is the entire Solidity code. Note that in the actual code that key string will be passed as a parameter, and not be hardcoded as it is done here:
// 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;
}
}