Python and Blockchain: Building a Simple Cryptocurrency Wallet

·

Creating a cryptocurrency wallet is one of the most practical and insightful ways to understand blockchain technology. A wallet does more than just store digital assets—it manages cryptographic keys, signs transactions, and interacts with decentralized networks. In this guide, we’ll walk through how to build a simple yet functional cryptocurrency wallet using Python, covering core components like key generation, address creation, transaction signing, and interaction with blockchain networks.

Whether you're a beginner exploring blockchain development or an experienced coder diving into decentralized systems, this tutorial provides hands-on knowledge that bridges theory and practice.


Core Components of a Cryptocurrency Wallet

A well-structured crypto wallet typically includes the following key elements:

These components form the foundation of any wallet, from basic tools to advanced multi-chain platforms.


Setting Up the Development Environment

Before coding begins, install essential Python libraries:

pip install ecdsa base58 cryptography web3 solcx

We’ll use:

👉 Discover powerful tools to test your blockchain applications in real-world environments.


Generating Key Pairs and Wallet Addresses

The heart of any wallet lies in secure key generation. We use the SECP256k1 curve—commonly used in Bitcoin and Ethereum—for generating cryptographically secure key pairs.

from ecdsa import SECP256k1, SigningKey
import hashlib
import base58

def generate_private_key():
    return SigningKey.generate(curve=SECP256k1)

def get_public_key(private_key):
    return private_key.get_verifying_key()

def get_address(public_key):
    public_key_bytes = public_key.to_string()
    sha256_hash = hashlib.sha256(public_key_bytes).digest()
    ripemd160_hash = hashlib.new('ripemd160', sha256_hash).digest()
    address = b'\x00' + ripemd160_hash  # Mainnet prefix
    checksum = hashlib.sha256(hashlib.sha256(address).digest()).digest()[:4]
    address += checksum
    return base58.b58encode(address).decode()

Run the code to generate your first wallet:

private_key = generate_private_key()
public_key = get_public_key(private_key)
address = get_address(public_key)

print(f"Private Key: {private_key.to_string().hex()}")
print(f"Public Key: {public_key.to_string().hex()}")
print(f"Address: {address}")
🔐 Never expose your private key—it grants full control over your funds.

Creating and Signing Transactions

A wallet must securely create and sign transactions. Below is a simplified example using JSON serialization and ECDSA signing.

import json

def create_transaction(from_addr, to_addr, amount, private_key):
    tx = {"from": from_addr, "to": to_addr, "amount": amount}
    tx_data = json.dumps(tx, sort_keys=True).encode()
    signature = private_key.sign(tx_data)
    return tx, signature.hex()

def verify_transaction(tx, sig_hex, public_key):
    tx_data = json.dumps(tx, sort_keys=True).encode()
    try:
        return public_key.verify(bytes.fromhex(sig_hex), tx_data)
    except:
        return False

# Example usage
to_address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"  # Bitcoin Genesis Address
tx, sig = create_transaction(address, to_address, 0.1, private_key)
print("Signature Valid:", verify_transaction(tx, sig, public_key))

This demonstrates how digital signatures ensure transaction authenticity without revealing the private key.


Broadcasting Transactions to the Blockchain

To send a transaction to the network, interact with a blockchain API:

import requests

def broadcast_transaction(transaction):
    url = "https://blockchain-api.example.com/broadcast"
    response = requests.post(url, json=transaction)
    return response.json()

In practice, you’d connect to real endpoints like Bitcoin Core RPC, Infura, or Alchemy for Ethereum.

👉 Explore leading platforms that support seamless blockchain integration and testing.


Securing Private Keys: Best Practices

Private key security is non-negotiable. Consider these strategies:

  1. Encrypted Local Storage: Use symmetric encryption (e.g., AES via Fernet) to protect keys at rest.
  2. Hardware Wallets: Store keys on dedicated devices (e.g., Ledger).
  3. Hierarchical Deterministic (HD) Wallets: Generate multiple keys from a seed phrase for easy backup.

Example: Encrypting Private Keys

from cryptography.fernet import Fernet

def encrypt_private_key(private_key, filename="encrypted_private_key.bin"):
    key = Fernet.generate_key()
    cipher = Fernet(key)
    encrypted = cipher.encrypt(private_key.to_string())
    
    with open(filename, "wb") as f:
        f.write(encrypted)
    with open("secret.key", "wb") as f:
        f.write(key)

def decrypt_private_key(filename="encrypted_private_key.bin"):
    with open("secret.key", "rb") as f:
        key = f.read()
    cipher = Fernet(key)
    with open(filename, "rb") as f:
        encrypted_data = f.read()
    decrypted = cipher.decrypt(encrypted_data)
    return SigningKey.from_string(decrypted, curve=SECP256k1)

Always store encryption keys separately from encrypted data.


Implementing Multisignature Transactions

Multisig wallets require multiple signatures to authorize a transaction—ideal for joint accounts or corporate treasuries.

def create_multisig_transaction(from_addr, to_addr, amount, private_keys):
    tx = {"from": from_addr, "to": to_addr, "amount": amount}
    tx_data = json.dumps(tx, sort_keys=True).encode()
    signatures = [key.sign(tx_data).hex() for key in private_keys]
    return tx, signatures

Verification ensures all required parties have signed. This enhances security by distributing trust.


Wallet Backup and Recovery

Backups prevent permanent loss due to hardware failure or accidental deletion.

import json

def backup_wallet(private_key, public_key, address):
    data = {
        "private_key": private_key.to_string().hex(),
        "public_key": public_key.to_string().hex(),
        "address": address
    }
    with open("wallet_backup.json", "w") as f:
        json.dump(data, f)

def restore_wallet(file_path):
    with open(file_path, "r") as f:
        data = json.load(f)
    pk_bytes = bytes.fromhex(data["private_key"])
    restored_pk = SigningKey.from_string(pk_bytes, curve=SECP256k1)
    restored_pub = get_public_key(restored_pk)
    return restored_pk, restored_pub, data["address"]

For production use, combine this with mnemonic seed phrases (BIP39 standard).


Interacting with Smart Contracts on Ethereum

Modern wallets go beyond sending coins—they interact with smart contracts. Using web3.py, we can call functions on Ethereum-based DApps.

from web3 import Web3

infura_url = "https://ropsten.infura.io/v3/YOUR_PROJECT_ID"
web3 = Web3(Web3.HTTPProvider(infura_url))

if not web3.is_connected():
    raise Exception("Connection failed")

# Load contract ABI and address
contract_abi = [...]  # From Solidity compilation
contract_address = "0xYourContractAddress"
contract = web3.eth.contract(address=contract_address, abi=contract_abi)

# Read data (no cost)
balance = contract.functions.balanceOf("0xUserAddress").call()
print("Token Balance:", balance)

# Send transaction (requires gas)
tx = contract.functions.transfer("0xRecipient", 100).build_transaction({
    'chainId': 3,
    'gas': 200000,
    'gasPrice': web3.to_wei('50', 'gwei'),
    'nonce': web3.eth.get_transaction_count("0xSenderAddress"),
})
signed_tx = web3.eth.account.sign_transaction(tx, private_key)
tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)

This enables integration with DeFi apps, NFT marketplaces, and DAOs.


Deploying Custom Tokens (ERC-20)

You can even deploy your own token using Python:

from solcx import compile_source

compiled_sol = compile_source('''
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
        _mint(msg.sender, initialSupply);
    }
}
''')

contract_interface = compiled_sol['<stdin>:MyToken']
MyToken = web3.eth.contract(abi=contract_interface['abi'], bytecode=contract_interface['bin'])

# Deploy
tx_hash = MyToken.constructor(1_000_000 * (10**18)).transact({
    'from': web3.eth.default_account,
})
tx_receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
print("Token deployed at:", tx_receipt.contractAddress)

Now you’ve created a fully functional ERC-20 token!


Advanced Features for Modern Wallets

As you expand functionality, consider adding:


Frequently Asked Questions (FAQ)

Q: Can I use this wallet for real funds?
A: While educational, this implementation lacks audit-grade security. Use established wallets like MetaMask or Ledger for real assets.

Q: Is Python suitable for production wallets?
A: Python is excellent for prototyping and learning. For production, consider languages like Rust or Go for better performance and security.

Q: What’s the difference between hot and cold wallets?
A: Hot wallets are internet-connected (e.g., mobile apps), while cold wallets (like hardware devices) store keys offline—offering superior protection.

Q: How do mnemonic phrases work?
A: They encode entropy into human-readable words (BIP39), allowing deterministic derivation of many keys from a single seed.

Q: Can I recover my wallet without a backup?
A: No—without the private key or seed phrase, recovery is impossible due to cryptographic design.

Q: Why use SECP256k1 instead of RSA?
A: SECP256k1 offers equivalent security with smaller key sizes and faster operations—ideal for blockchains.


Final Thoughts

Building a cryptocurrency wallet in Python unlocks deep understanding of blockchain mechanics—from cryptography to decentralized application interaction. While this version serves as a learning tool, it lays the groundwork for developing robust, feature-rich wallets.

As blockchain evolves with trends like Web3, DeFi, and tokenization, developers who master these fundamentals will lead innovation.

👉 Start experimenting with blockchain APIs and tools to bring your wallet ideas to life.