javascriptcryptographyethereumsecp256k1js-ethereum-cryptography

Ethereum: how to generate a valid address from the public key?


I am using this code to generate a private key, public key and address, acording to this:

The public key is generated from the private key using the Elliptic Curve Digital Signature Algorithm. You get a public address for your account by taking the last 20 bytes of the Keccak-256 hash of the public key and adding 0x to the beginning.

const { secp256k1 } = require("ethereum-cryptography/secp256k1");
const { keccak256 } = require("ethereum-cryptography/keccak");
const { toHex } = require("ethereum-cryptography/utils");

const privateKey = secp256k1.utils.randomPrivateKey();
console.log('private key :  ', toHex(privateKey));

const publicKey = secp256k1.getPublicKey(privateKey);
console.log('public key  :', toHex(publicKey));

const address = keccak256(publicKey.slice(1)).slice(-20);
console.log('address     :', '0x' + toHex(address));

However, I get a valid private and public key pair, but not the corresponding address (compared to some online converters like this and this).


Solution

  • The bug in your code is that you generate the address on the basis of the compressed key, but it must be generated on the basis of the uncompressed key (0x04|x|y).
    The leading marker byte has to be removed, from the rest, i.e. the 32 bytes x coordinate and the 32 bytes y coordinate, the Keccak-256 hash is generated, the last 20 bytes of which are the address.

    Code with sample data:

    const { secp256k1 } = require("ethereum-cryptography/secp256k1");
    const { keccak256 } = require("ethereum-cryptography/keccak");
    const { toHex } = require("ethereum-cryptography/utils");
    
    const privateKey = secp256k1.utils.randomPrivateKey();
    console.log('private key        :  ', toHex(privateKey));               // c8aee432ef2035adc6f71a7094c0677eedf74a04f4e17227fa1a4155ad511047
    
    const publicKeyComp = secp256k1.getPublicKey(privateKey, true);         // 0262117d6727ddd50b8f1d60ce50ef9fa511c7b43b6b6e6f763b32b942e515a4d4
    console.log('public key - comp  :', toHex(publicKeyComp));
    
    const publicKeyUncomp = secp256k1.getPublicKey(privateKey, false);      // 0462117d6727ddd50b8f1d60ce50ef9fa511c7b43b6b6e6f763b32b942e515a4d47df6eb61d3dceb615176c80a16484e773885f3de31e0344ed3d74cce103646f4
    console.log('public key - uncomp:', toHex(publicKeyUncomp));
    
    const address = keccak256(publicKeyUncomp.slice(1)).slice(-20);
    console.log('address            :', '0x' + toHex(address));             // 0x9cea81b9d2e900d6027125378ee2ddfa15feeed1
    

    Note that the current solution does not yet implement the checksum for Ethereum addresses (EIP-55).
    This functionality is provided e.g. by the NodeJS package eip55 and can be applied as followed:

    const { encode } = require('eip55')
    ...
    const addressEip55 = encode('0x' + toHex(address));
    console.log('address (EIP-55)   :', addressEip55);                      // 0x9cea81B9D2E900d6027125378ee2ddfA15FeEED1
    

    This code now generates the same address as the website you referenced (here), if the public key (compressed or uncompressed, with leading marker byte) is specified there.