Creating An Ethereum Account

Learn how to generate an Ethereum account (private key and public key)

Ethereum account is an address, yes. The address is derived from the public key. And the public key is derived from the private key. You can create an Ethereum account by using eth-keys library. You would get this library if you had Mamba (black-mamba) installed. Okay, let's play around with eth-keys module.


$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ pip install eth-keys

Create a script called create_account.py. You can also run Python console and start from there.

First thing first, we need to import some functions.


import codecs
import os

from eth_account import Account
from eth_keys.backends.native.ecdsa import private_key_to_public_key
from eth_keys.datatypes import PrivateKey, PublicKey
from eth_keys.utils.address import public_key_bytes_to_address
from eth_utils import keccak, to_normalized_address
from eth_utils.hexadecimal import encode_hex

Most of the time you only need to use eth_account module (high-level module). Other modules are quite low-level. I import them to explain some concepts.

To create an Ethereum account, you would use Account.create method.


print("Account from Account.create")
account = Account.create("extra entropy")
print(account)
print(f"Key {account.key}")
print(f"Address {account.address}")

The Account.create method accepts an optional entropy. Basically a private key is a random 256 bits number. 1 bit is either 1 or 0. Andreas M. Antonopoulos likened choosing a private key (in Bitcoin) as tossing coin 256 times. Head is 1, tail is 0. Entropy is to harden the randomness. If the generation of the private key was not random enough, it would be disastrous. You can pass random string as entropy if you have a strong random utility (either from a machine or a random number generator). The result is a LocalAccount object on which you can get the private key (account.key) and the address (account.address). Run the script.


(.venv) $ python create_account.py
Account from Account.create

Key b'q\xd8\xca\x836\xfb\x8cp\x05\x07\xc5\xc4\xef\x17\xceW\xca\xdaaK$l\x96\x7fX\xa6\xe8\x98\xe6+`\x92'
Address 0x7Ec1229197F700FC9190850bEdf75D78cAE8E914

That's it. You can ask your friend to send some ETH to your new address.

Let's write additional code so I can show you that to generate a private key is the same as generating a random number (256 bit or 32 bytes).


print("=========")
print("Private key is random bytes")
random_bytes = os.urandom(32)
print(f"Random bytes : {random_bytes}")
print(f"Hex random bytes: {encode_hex(random_bytes)}")
hex_random_bytes = '0x' + codecs.decode(codecs.encode(random_bytes, 'hex'), 'ascii')
print(f"Hex random bytes: {hex_random_bytes}")
print(f"Random bytes in bits: {bin(int(hex_random_bytes, base=16))}")

Python has os.urandom method to generate a random number. The parameter is the size of the random number. Here, you put 32 bytes. But as you saw previously, our private key is in bytes mode. If you want to see it in hexadecimal mode (a nicer format), you can use encode_hex function from eth_utils.hexadecimal. Basically it's same as if you encode the bytes to hex and decode it with ascii format. Lastly, if you want to see your private key in bits mode, you can use the combination of bin and int functions. Run it and you would get this additional output.


=========
Private key is random bytes
Random bytes : b'\xf2\x18\xda\xef\xf2\x90<2\x13\xa5\xa6Q\x9a\x81\xcd\xa83\xe3\xb7\xd0\xe5\xd1X1\x1d\xba\xd5.\xde\xa2\xbc\xd0'
Hex random bytes: 0xf218daeff2903c3213a5a6519a81cda833e3b7d0e5d158311dbad52edea2bcd0
Hex random bytes: 0xf218daeff2903c3213a5a6519a81cda833e3b7d0e5d158311dbad52edea2bcd0
Random bytes in bits: 0b1111001000011000110110101110111111110010100100000011110000110010000100111010010110100110010100011001101010000001110011011010100000110011111000111011011111010000111001011101000101011000001100010001110110111010110101010010111011011110101000101011110011010000

Now that you understand the private key, usually to make it more secure you hash the random number. Remember, hashing is a one-way function. Hashing with keccak gives you a 32 bytes number. So you can generate an Ethereum account by hashing a short string. Let's write additional code.


print("=========")
print("Account from Account.from_key")
account2 = Account.from_key(keccak(b"abc"))
print(account2)
account = Account.from_key(random_bytes)
print(f"Key of account is private key, {account.key}")
private_key_obj = PrivateKey(random_bytes)
print(f"Private key object: {private_key_obj}")
print(f"Backend of the private key: {private_key_obj.backend}")
print(f"Public key (uncompressed): {private_key_obj.public_key}")
public_key_obj = PublicKey(private_key_obj.public_key.to_bytes())
print(f"Public key object: {public_key_obj}")
print(f"Address: {public_key_obj.to_address()}")
print(f"Checksum address: {public_key_obj.to_checksum_address()}")

You can create an Ethereum account by using Account.from_key method only if you got your private key already. So instead of using Account.create, you can generate your own random 32 bytes number and use Account.from_key method.

You can create a private key object using PrivateKey. You need to pass a 32 bytes number as the parameter. PrivateKey also accepts optional backend parameter as the second parameter, but you don't need to worry about it now. The PrivateKey object has a public key property. It also has to_address and to_checksum_address methods. Checksum address is same as the normal address; only some alphabets are in uppercase mode. Run the script.


=========
Account from Account.from_key

Key of account is private key, b'\xf2\x18\xda\xef\xf2\x90<2\x13\xa5\xa6Q\x9a\x81\xcd\xa83\xe3\xb7\xd0\xe5\xd1X1\x1d\xba\xd5.\xde\xa2\xbc\xd0'
Private key object: 0xf218daeff2903c3213a5a6519a81cda833e3b7d0e5d158311dbad52edea2bcd0
Backend of the private key: 
Public key (uncompressed): 0x581967857266776ac0bfc6d4eba34a20a08619e39ea5943bc058971d99393773ba15c78c35150a2d8f1436ed656473edbae15fbe2d5f18e0c0b90a4fa3d4672f
Public key object: 0x581967857266776ac0bfc6d4eba34a20a08619e39ea5943bc058971d99393773ba15c78c35150a2d8f1436ed656473edbae15fbe2d5f18e0c0b90a4fa3d4672f
Address: 0x6896243ca3c08b736aafa3945d0940f0dc182a04
Checksum address: 0x6896243CA3c08b736aaFA3945d0940f0dC182a04

So how do you generate a public key from a private key actually? Let's write additional code.


print("=========")
print("Public key from random bytes")
public_key = private_key_to_public_key(random_bytes)
address = public_key_bytes_to_address(public_key)
normalized_address = to_normalized_address(address)
print(normalized_address)

You can use private_key_to_public_key function from eth_keys.backends.native.ecdsa. How it converts the private key to the public key involves complex mathematics functions (finite fields, modulo multiplication with really really big numbers). I'll cover it in future documentation.

To convert the public key to an Ethereum address, you can use public_key_bytes_to_address from eth_keys.utils.address. The address is in bytes mode. Then to convert the address in bytes mode to a normalized address (hexadecimal mode), you can use to_normalized_address from eth_utils. Run it.


0x6896243ca3c08b736aafa3945d0940f0dc182a04

Here is the full source code.


# SPDX-License-Identifier: MIT
import codecs
import os

from eth_account import Account
from eth_keys.backends.native.ecdsa import private_key_to_public_key
from eth_keys.datatypes import PrivateKey, PublicKey
from eth_keys.utils.address import public_key_bytes_to_address
from eth_utils import keccak, to_normalized_address
from eth_utils.hexadecimal import encode_hex


print("=========")
print("Account from Account.create")
account = Account.create("extra entropy")
print(account)
print(f"Key {account.key}")
print(f"Address {account.address}")

print("=========")
print("Private key is random bytes")
random_bytes = os.urandom(32)
print(f"Random bytes : {random_bytes}")
print(f"Hex random bytes: {encode_hex(random_bytes)}")
hex_random_bytes = '0x' + codecs.decode(codecs.encode(random_bytes, 'hex'), 'ascii')
print(f"Hex random bytes: {hex_random_bytes}")
print(f"Random bytes in bits: {bin(int(hex_random_bytes, base=16))}")

print("=========")
print("Account from Account.from_key")
account2 = Account.from_key(keccak(b"abc"))
print(account2)
account = Account.from_key(random_bytes)
print(f"Key of account is private key, {account.key}")
private_key_obj = PrivateKey(random_bytes)
print(f"Private key object: {private_key_obj}")
print(f"Backend of the private key: {private_key_obj.backend}")
print(f"Public key (uncompressed): {private_key_obj.public_key}")
public_key_obj = PublicKey(private_key_obj.public_key.to_bytes())
print(f"Public key object: {public_key_obj}")
print(f"Address: {public_key_obj.to_address()}")
print(f"Checksum address: {public_key_obj.to_checksum_address()}")

print("=========")
print("Public key from random bytes")
public_key = private_key_to_public_key(random_bytes)
address = public_key_bytes_to_address(public_key)
normalized_address = to_normalized_address(address)
print(normalized_address)