How to Build a Minimalistic Ethereum Wallet in Python

·

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.7

Import the necessary modules:

from tinyec.ec import SubGroup, Curve
from Crypto.Random.random import randint
from web3 import Web3

Step 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 multiplication

Step 2: Deriving the Ethereum Address

  1. Concatenate Public Key Coordinates:

    public_key_hex = Web3.toHex(public_key.x)[2:] + Web3.toHex(public_key.y)[2:]
  2. Apply Keccak-256 Hash:

    address = "0x" + Web3.keccak(hexstr=public_key_hex).hex()[-40:]
  3. 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:

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!