A large number of articles on the Internet have introduced ECDSA (elliptic curve encryption) algorithm to generate Ethereum public-private key pairs, and then generate a unique Ethereum address. Most of them mentioned that the uncompressed public key generates the address by hashing first, and then the last 40 bits are the address. However, I must bow to know this. I believe many people don't know how to hash and what the format of public key and private key is like me!
Because I am studying something, I need to understand the detailed process of Ethereum public key address generation. The author first Baidu, referring to the above article Private key, public key, address and account of Ethereum . However, after groping for a long time, I couldn't get the results listed in the article. Finally, after the author imported the private key in the article into MetaMask, it was surprising that the final address was not the example address in the article. It seems that something must have gone wrong, so the author decided to find out the clear generation process from public key to address, which can no longer be vague.
There is a flow chart in this article:
The flow chart is very correct. There are only three simple steps, but the result is incorrect. The most likely error should be from the public key to the compressed public key through keccak-256 algorithm. What is the specific keccak-256 function? Different languages have different implementations. Here I still use the most commonly used node JS and ethers framework to verify this problem.
Fortunately, there is a function in the ethers framework that directly uses the public key to calculate the address. The following is the definition and description of the function:
ethers.utils.computeAddress( publicOrPrivateKey ) ⇒ string< Address >source Returns the address for publicOrPrivateKey. A public key may be compressed or uncompressed, and a private key will be converted automatically to a public key for the derivation.
This is the encapsulated computing library. If we want to know the detailed process, we can get it by referring to its source code implementation. I won't talk about the specific reference process here. The script for successful verification is directly posted below (assuming it's called test.sol):
//Detailed process of generating address instance according to public key const eccrypto = require("eccrypto"); const sha3 = require("js-sha3"); const {ethers,utils} = require("ethers") const private_key = "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725"; const my_wallet = new ethers.Wallet(private_key) const HexCharacters = "0123456789abcdef"; const public_key = my_wallet.publicKey printPublicKey(public_key) //Step 1: remove the first two digits 04 of the public key. If 0x is included, the four digits will be removed, and the 0x structure will be added again let new_key = "0x" + public_key.substring(4) //Step 2: convert the above results into byteslike (no leakage) let new_bytes = utils.arrayify(new_key) //Step 3, keccak_256 to get a hash value with a length of 64 new_key = sha3.keccak_256(new_bytes) //Step 4: take the last 40 bits of the above result to get the address in all lowercase. let result = "0x" + new_key.substring(24) //Finally, the address is converted to the verified address result = utils.getAddress(result) console.log("") console.log(result) console.log(result === my_wallet.address) function printPublicKey(public_key) { console.log(public_key.substring(2,4)) let half = (public_key.length - 4)/2 console.log(public_key.substring(4, 4+half)) console.log(public_key.substring(4+half)) } //unused function convertBytesToHexString(value) { let result = "0x"; for (let i = 0; i < value.length; i++) { let v = value[i]; result += HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f]; } return result; }
As can be seen from the above code, there are several steps to generate addresses using public keys:
- Remove the prefix 04 of the public key, which is mentioned in the article:
The elliptic curve digital signature algorithm ECDSA-secp256k1 is used to map the private key (32 bytes) to the public key (65 bytes) (prefix 04+X public key + Y public key):
However, it does not mention removing the prefix when calculating Keccak-256.
- Convert the hexadecimal string after 04 removed above into a byte array again
- Keccak the byte array obtained in the previous step_ 256 operation to obtain a hash value with a length of 64 (compressed public key, 32 bytes).
- Take the last 40 bits of the above result and get the address in all lowercase.
- Optional. Convert all lowercase addresses to verified addresses.
We directly node test JS will get the following output:
04 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352 2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6 0x3E9003153d9A39D3f57B126b0c38513D5e289c3E true
The output in this format is for comparison with the original article.
As for why the final address in the original article is wrong, the author repeats it here, because it directly performs Keccak-256 operation on the public key string after 04 is removed, but does not convert it into a byte array.