0

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

1 Answer 1

4

The problem is that abi.encodePacked(key) interprets the key as a string value, not as a hexadecimal value. While there are ways to convert a hexadecimal string into bytes in Solidity, it's much easier to declare it as bytes in the first place:

bytes memory publicKey = hex"3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb";

Then you can simply cast the result to an address to get the address from the full Keccak-256 hash:

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

Calling this results in 0x622CF04ee8659bC45d76deF393077Ddcc5396761 as expected.

7
  • Is there a reason to use assembly here? why not just -> address(uint160(uint256(keccak256(bytes(hex"3b88b538dff7db813b6c8be6bfce81f6dd9d820213fe9211e9f5a631c360c7ddbb26690ae40eac62e0b5aaf2d8a5c4287e3c383fc1c00916ce12e354e1eb12eb")))));
    – Majd TL
    Jun 16, 2021 at 12:33
  • I copied that from the original question, but you're right that that is a bit simpler (and very slightly more gas efficient). I've updated my answer.
    – Morten
    Jun 16, 2021 at 13:08
  • Hi, thanks for the answer, that clarifies things for me a lot. Anyhow one small followup-question: As in the example, the key string was hardcoded for testing purposes, in the actual code, I'll have it passed as a parameter (string memory). hex"abcd..." then can't be used. Unfortunately, no function hex() or so seem to exist. Does one have to go through the extensive "manual" transcoding in the post linked by you or is there another way?
    – Xenonite
    Jun 16, 2021 at 13:55
  • Can't you simply pass the public key as bytes memory? I'm not too familiar with Web3j, but I'd assume it has a way to do that, and converting a hexadecimal string to bytes should be a lot easier (and more efficient) in Java than in Solidity.
    – Morten
    Jun 16, 2021 at 14:07
  • That is the other option. Anyhow, I need a way to pass a string-formatted key
    – Xenonite
    Jun 16, 2021 at 14:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.