Introduction
In this tutorial, we'll build a simple Ethereum wallet from scratch using Python. By leveraging cryptography, we'll generate an Ethereum-compatible key pair, derive the wallet address, and encrypt the private key with a password. This guide focuses on Python libraries like web3, tinyec, and PyCryptodome for blockchain interaction and security.
Prerequisites
Ensure the following Python packages are installed:
pip install web3==5.6.0 tinyec==0.3.1 PyCryptodome==3.9.7Import the necessary modules:
from tinyec.ec import SubGroup, Curve
from Crypto.Random.random import randint
from web3 import Web3Step 1: Generating an Ethereum Key Pair
Ethereum uses Elliptic Curve Cryptography (ECC) based on the secp256k1 standard. Here’s how to configure the curve parameters:
Define Curve Parameters
Convert hexadecimal values from the SECP256k1 standard to integers:
p = int("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16)
n = int("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)Configure Elliptic Curve
x = int("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
y = int("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
g = (x, y)
field = SubGroup(p, g, n, h=1)
curve = Curve(a=0, b=7, field=field, name='secp256k1')Generate Private and Public Keys
private_key = randint(1, n)
public_key = private_key * curve.g # Elliptic curve point multiplicationStep 2: Deriving the Ethereum Address
Concatenate Public Key Coordinates:
public_key_hex = Web3.toHex(public_key.x)[2:] + Web3.toHex(public_key.y)[2:]Apply Keccak-256 Hash:
address = "0x" + Web3.keccak(hexstr=public_key_hex).hex()[-40:]Add Checksum for Error Detection:
address = Web3.toChecksumAddress(address)
👉 Explore advanced wallet security practices
Step 3: Encrypting the Private Key with a Password
Use AES-256 and scrypt for secure encryption:
Key Derivation with scrypt
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import scrypt
password = b"your_password"
salt = get_random_bytes(16)
key = scrypt(password, salt, 32, N=2**20, r=8, p=1)Encrypt Private Key
cipher = AES.new(key, AES.MODE_CBC)
ct_bytes = cipher.encrypt(pad(Web3.toHex(private_key)[2:].encode('utf-8'), AES.block_size))Save to JSON File
import json
output = {
"salt": salt.hex(),
"iv": cipher.iv.hex(),
"encrypted_key": ct_bytes.hex()
}
with open(f'{address}.json', 'w') as f:
json.dump(output, f)Step 4: Decrypting the Private Key
with open(f'{address}.json') as f:
data = json.load(f)
salt = bytes.fromhex(data['salt'])
iv = bytes.fromhex(data['iv'])
ct = bytes.fromhex(data['encrypted_key'])
key = scrypt(password, salt, 32, N=2**20, r=8, p=1)
cipher = AES.new(key, AES.MODE_CBC, iv)
private_key = unpad(cipher.decrypt(ct), AES.block_size).decode('utf-8')FAQs
Q1: What is the role of the salt in scrypt?
A1: The salt prevents precomputed rainbow table attacks by ensuring uniqueness even for identical passwords.
👉 Learn more about cryptographic salts
Q2: Why use Keccak-256 for Ethereum addresses?
A2: Keccak-256 (a SHA-3 variant) is Ethereum’s chosen hash function for generating deterministic, collision-resistant addresses.
Q3: How secure is AES-256 for private keys?
A3: AES-256 is a military-grade encryption standard when paired with a strong password and proper key derivation (e.g., scrypt).
Conclusion
In Part 1, we’ve covered:
- Generating an ECC key pair using
secp256k1. - Deriving an Ethereum address via Keccak-256 hashing.
- Encrypting the private key with AES-256 and scrypt.
In Part 2, we’ll integrate this wallet with the Ethereum blockchain using web3.py for transactions and balance checks.
Stay tuned for more blockchain development guides!