Ethereum Wallet Toolkit — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Ethereum Wallet Toolkit (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This directory contains the build system to create offline1.html using official ethereumjs libraries.
All libraries are from the official Ethereum ecosystem:
@ethereumjs/tx - Transaction signing (legacy + EIP-1559)@ethereumjs/util - Utilities (address validation, checksums, etc.)@ethereumjs/rlp - RLP encoding@ethereumjs/wallet - Wallet utilitiesethereum-cryptography - Official cryptographic primitives (secp256k1, keccak256)@scure/bip39 - BIP39 mnemonic (used by ethereumjs)@scure/bip32 - BIP32 HD derivation (used by ethereumjs)# Navigate to this directory
cd offline-build
# Install dependencies
npm install
# Build offline1.html
npm run buildThe output file offline1.html will be created in the parent directory.
The build process uses esbuild to:
Typical bundle size: ~400-600 KB (varies by version)
All features match the CLI tool:
This repository includes 5 Model Context Protocol (MCP) servers that expose Ethereum wallet functionality to AI assistants like Claude.
| Server | Purpose | Tools | Tests |
|---|---|---|---|
| ethereum-wallet-mcp | Wallet generation, HD wallets, mnemonics | 6 | 111 |
| keystore-mcp-server | Encrypted keystore files (Web3 Secret Storage) | 9 | 74 |
| signing-mcp-server | Message signing, EIP-191, EIP-712 | 12 | 34 |
| transaction-mcp-server | Transaction building, encoding, signing | 15 | 65 |
| validation-mcp-server | Address/key validation, checksums, hashing | 15 | 64 |
Total: 57 tools, 348 tests
Each server is a standalone Python package:
# Install all servers
pip install -e ./ethereum-wallet-mcp
pip install -e ./keystore-mcp-server
pip install -e ./signing-mcp-server
pip install -e ./transaction-mcp-server
pip install -e ./validation-mcp-serverAdd to your claude_desktop_config.json:
{
"mcpServers": {
"ethereum-wallet": {
"command": "ethereum-wallet-mcp"
},
"keystore": {
"command": "keystore-mcp-server"
},
"signing": {
"command": "signing-mcp-server"
},
"transaction": {
"command": "transaction-mcp-server"
},
"validation": {
"command": "validation-mcp-server"
}
}
}Wallet Generation & HD Wallets
Tools:
generate_wallet - Random Ethereum walletgenerate_wallet_with_mnemonic - BIP39 mnemonic walletrestore_wallet_from_mnemonic - Restore from seed phraserestore_wallet_from_private_key - Restore from private keyderive_multiple_accounts - HD wallet derivationgenerate_vanity_address - Custom prefix/suffix addressesResources:
wallet://documentation/bip39 - BIP39 specwallet://documentation/derivation-paths - HD pathswallet://wordlist/{language} - BIP39 wordlistsEncrypted Keystore Files (Web3 Secret Storage)
Tools:
encrypt_keystore - Create encrypted keystore from private keydecrypt_keystore - Decrypt keystore to get private keychange_keystore_password - Re-encrypt with new passwordvalidate_keystore - Verify keystore structureget_keystore_address - Extract address without decryptiongenerate_encrypted_wallet - Create new wallet as keystoreread_keystore_file - Read keystore from filesystemwrite_keystore_file - Save keystore to filesystemSupports:
Message & Data Signing
Tools:
sign_message - EIP-191 personal_signverify_message - Verify signed messagehash_message - Create message hashsign_typed_data - EIP-712 structured dataverify_typed_data - Verify typed data signatureencode_typed_data - Encode without signingrecover_signer - Recover address from signatureTransaction Building & Signing
Tools:
build_transaction - Create unsigned transactionsign_transaction - Sign with private keydecode_transaction - Parse raw transactionencode_transaction - RLP encode transactionestimate_gas - Calculate gas requirementscalculate_transaction_hash - Pre-signing hashbuild_eip1559_transaction - Type 2 transactionsbuild_legacy_transaction - Type 0 transactionsbuild_access_list_transaction - Type 1 transactionsserialize_transaction - Convert to wire formatparse_transaction_input - Decode calldatavalidate_transaction - Check transaction validityValidation & Cryptographic Utilities
Tools:
validate_address - EIP-55 checksum validationvalidate_private_key - Key range checkingto_checksum_address - Convert to checksummedderive_address_from_private_key - Key → addressderive_address_from_public_key - Pubkey → addressvalidate_signature - Check v, r, s valuesvalidate_hex_data - Hex string validationcompare_addresses - Address equalitybatch_validate_addresses - Bulk validationgenerate_vanity_check - Pattern matchingkeccak256_hash - Compute Keccak-256encode_function_selector - Signature → selectordecode_function_selector - Lookup selectorsvalidate_ens_name - ENS format validationcalculate_storage_slot - Storage slot computationResources:
validation://eip55-specification - EIP-55 docsvalidation://secp256k1-constants - Curve paramsvalidation://function-selectors-db - 500+ selectorsvalidation://address-patterns - Known patternsRun all server tests:
# Individual servers
pytest ethereum-wallet-mcp/tests/ -v
pytest keystore-mcp-server/tests/ -v
pytest signing-mcp-server/tests/ -v
pytest transaction-mcp-server/tests/ -v
pytest validation-mcp-server/tests/ -v
# All at once
./run_all_tests.shAll servers follow a consistent pattern:
server-name/
├── pyproject.toml # Package config
├── README.md # Server docs
├── src/
│ └── package_name/
│ ├── __init__.py
│ ├── __main__.py # Entry point
│ ├── server.py # MCP server setup
│ ├── tools/ # Tool implementations
│ ├── resources/ # Static resources
│ └── prompts/ # Interactive prompts
└── tests/
└── test_*.py # Pytest testsEach tool has:
*_impl() function with pure business logic (for testing)⚠️ Warning: These tools handle sensitive cryptographic material. Review the code and understand the implications before using with real assets.
All servers use official Ethereum Foundation libraries:
eth-account - Key generation, signingeth-keys - ECDSA operationseth-utils - Utility functionseth-rlp - RLP encodingmnemonic - BIP39 implementationmcp - Model Context Protocol SDKMIT License - See LICENSE
This guide provides real-world prompt examples for interacting with the Ethereum Wallet Toolkit MCP servers through AI assistants like Claude.
Simple:
Generate a new Ethereum wallet for meWith mnemonic:
Create a new Ethereum wallet with a 24-word seed phraseFor development/testing:
Generate a test wallet for Sepolia testnet developmentFrom mnemonic:
Restore my wallet from this seed phrase:
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon aboutFrom private key:
Import this private key and show me the wallet address:
0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318Derive multiple accounts:
Derive 5 accounts from this mnemonic using the standard Ethereum path:
[your 12/24 word mnemonic]Custom derivation path:
Derive an account at path m/44'/60'/1'/0/0 from my seed phraseSimple prefix:
Generate a vanity address starting with "cafe"Case-insensitive suffix:
Find me an address ending with "1337" (case insensitive)Basic signing:
Sign this message with my private key:
Message: "Hello, Ethereum!"
Private key: 0x...For verification:
Sign a message that proves I own wallet 0xABC...
The message should be: "I authorize login to MyDApp on 2024-01-15"Verify message:
Verify this signed message:
- Message: "Hello, Ethereum!"
- Signature: 0x...
- Expected signer: 0x...Recover signer:
Who signed this message?
- Message: "I agree to the terms"
- Signature: 0x...Decompose signature:
Break down this signature into v, r, s components:
0x...Normalize v value:
Convert this signature's v value from 0/1 format to 27/28 format:
0x...Permit signature for token approval:
Sign an EIP-712 permit for USDC on Ethereum mainnet:
- Owner: 0x... (my address)
- Spender: 0x... (Uniswap router)
- Value: 1000000000 (1000 USDC, 6 decimals)
- Nonce: 0
- Deadline: 1735689600 (Unix timestamp)
- Private key: 0x...
- Contract address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48NFT marketplace order:
Sign this EIP-712 typed data for an NFT listing:
Domain:
- Name: "OpenSea"
- Version: "1.4"
- Chain ID: 1
- Verifying Contract: 0x00000000006c3852cbEf3e08E8dF289169EdE581
Message (Order):
- offerer: 0x... (my address)
- zone: 0x0000000000000000000000000000000000000000
- offer: [{ token: 0x..., identifier: 1234, amount: 1 }]
- consideration: [{ token: 0x0, amount: 1000000000000000000 }]
- orderType: 0
- startTime: 1704067200
- endTime: 1735689600
Private key: 0x...Compute the EIP-712 hash for this typed data without signing:
[typed data structure]Simple ETH transfer:
Build and sign a transaction to send 0.5 ETH:
- To: 0x742d35Cc6634C0532925a3b844Bc9e7595f5b4E2
- Chain ID: 1 (mainnet)
- Nonce: 42
- Max fee per gas: 30 gwei
- Max priority fee: 2 gwei
- Private key: 0x...ERC-20 token transfer:
Create a signed transaction to transfer 100 USDT:
- Token contract: 0xdAC17F958D2ee523a2206206994597C13D831ec7
- To: 0x...
- Amount: 100000000 (100 USDT with 6 decimals)
- From nonce: 5
- Chain ID: 1
- Gas limit: 65000
- Max fee: 50 gwei
- Private key: 0x...Contract interaction:
Build a transaction to call the 'approve' function:
- Contract: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 (USDC)
- Function: approve(address spender, uint256 amount)
- Spender: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D (Uniswap)
- Amount: max uint256 (unlimited approval)
- Sign with: 0x...Decode raw transaction:
Decode this raw signed transaction and show me what it does:
0x02f8730180843b9aca00850...Decode calldata:
Decode this calldata and tell me what function it calls:
0xa9059cbb000000000000000000000000...Estimate cost:
Estimate the total cost in ETH for this transaction at current gas prices:
- Gas limit: 21000
- Max fee: 30 gwei
- Priority fee: 2 gweiCompare transactions:
Compare these two transactions and highlight the differences:
Transaction 1: 0x...
Transaction 2: 0x...From private key:
Create an encrypted keystore file for this private key:
- Private key: 0x...
- Password: MySecurePassword123!
- Use scrypt (more secure)Generate new encrypted wallet:
Generate a new wallet and immediately encrypt it as a keystore:
- Password: MySecurePassword123!
- Return the keystore JSONGet private key:
Decrypt this keystore to get the private key:
[paste keystore JSON]
Password: MySecurePassword123!Just get the address:
What's the address in this keystore? (don't decrypt)
[paste keystore JSON]Change password:
Change the password on this keystore:
[paste keystore JSON]
Old password: OldPassword123
New password: NewSecurePassword456!Validate keystore:
Is this a valid Web3 Secret Storage keystore?
[paste keystore JSON]Single address:
Is this a valid Ethereum address? 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAedCheck checksum:
Does this address have a valid EIP-55 checksum?
0x5aaEB6053f3e94C9b9A09f33669435e7ef1beaedConvert to checksum:
Convert this address to checksummed format:
0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaedBatch validation:
Check which of these addresses are valid:
- 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed
- 0x1234
- 0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359
- invalidValidate key:
Is this a valid Ethereum private key?
0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318Security check:
Check if this private key is secure (not a known weak key):
0x0000000000000000000000000000000000000000000000000000000000000001Keccak256 hash:
Compute the keccak256 hash of "Hello, Ethereum!"Function selector:
What's the function selector for transfer(address,uint256)?Decode selector:
What function does selector 0xa9059cbb correspond to?Calculate mapping slot:
Calculate the storage slot for a mapping at slot 2 with key 0xABC...Dynamic array slot:
Calculate the storage slot for the first element of a dynamic array at slot 5Help me create a complete backup of my wallet:
1. Generate a new wallet with mnemonic
2. Create an encrypted keystore as additional backup
3. Show me how to verify I can restore from bothWalk me through creating a gasless ERC-20 permit:
1. I want to approve Uniswap to spend my USDC
2. Generate the EIP-712 typed data
3. Sign it with my private key
4. Show me the permit parameters to submit on-chainSet up a hierarchical wallet structure:
1. Generate a master seed phrase (24 words)
2. Derive 3 accounts:
- Account 0: Main spending wallet
- Account 1: Savings (cold storage)
- Account 2: DeFi interactions
3. Create keystores for each with different passwordsAudit the security of this private key:
0x...
Check for:
- Known weak keys
- Proper entropy
- Valid curve pointI have a transaction that failed. Help me debug it:
Raw transaction: 0x...
Please:
1. Decode the transaction completely
2. Validate all fields
3. Identify potential issues
4. Suggest fixes✅ "Sign a transaction for Ethereum mainnet (chain ID 1)"
❌ "Sign a transaction"✅ "Sign message 'Hello' with private key 0x..."
❌ "Sign a message"✅ "Return the signature in v/r/s format as well as the packed format"
❌ "Sign and give me the signature"✅ "Generate a test wallet for development"
❌ "Generate a wallet" (when just testing)✅ "Generate a wallet, then create a signed message proving ownership"
❌ Two separate prompts that lose context⚠️ When using these prompts:
DISCLAIMER: FOR EDUCATIONAL PURPOSES ONLY
>
This software is provided for educational and research purposes only. DO NOT USE WITH REAL FUNDS. The author accepts NO LIABILITY for any damages. All example addresses shown are for illustrative purposes only.
A comprehensive compilation of research papers, technical articles, and security analyses related to cryptocurrency vanity addresses, their generation, vulnerabilities, and associated attack vectors.
Vanity addresses are custom cryptocurrency wallet addresses created using specific algorithms to incorporate user-chosen character sequences. While they serve legitimate purposes such as gas optimization, protocol branding, and multichain reproducibility, they have also been associated with significant security vulnerabilities and attack vectors.
This document compiles research from academic papers, security analyses, and technical blog posts to provide a comprehensive understanding of vanity addresses in the cryptocurrency ecosystem.
When generating a cryptocurrency wallet, the system produces an address composed of a random string of characters. These default addresses lack personal significance. A vanity address is a custom address generated using specific algorithms that incorporate a user-chosen sequence of characters into the wallet address.
0x1111... or 0x0000... for branding.Creating vanity addresses is computationally intensive. The algorithm must try many combinations before finding an address that includes the chosen string of characters. The higher the number of prefixes and suffixes requested, the more time and computational resources required.
For reference, using optimized GPU mining (RTX 3090 at ~2 billion attempts/second):
Profanity is an open-source vanity address generator. The basic workflow:
Critical Flaw: The profanity code uses a pseudo-random number generator called mt19937, which only outputs 8 bytes at a time and takes a 4-byte unsigned int seed (fed by a random_device call). Since Ethereum private keys are 32 bytes, the code must combine 4 outputs. The mt19937_64 generator is only seeded once, so outputs don't change if the input seed is reused. This reduces complexity from 2^256 to 2^32 (James).
CREATE (Default):
new_address = hash(sender, nonce)CREATE2 (Recommended for Vanity):
new_address = hash(0xFF, sender, salt, bytecode)The fundamental flaw in Profanity's random number generation:
// Vulnerable code from Dispatcher.cpp
// Uses mt19937 with only 4-byte seed
// Reduces keyspace from 2^256 to 2^32Impact: All starting private keys that could be generated by this program can be generated and saved in just a few hours using less than 2TB of hard drive space (James).
EOAs (Externally Owned Accounts):
Smart Contract Accounts:
As stated by foobar: "EOA vanity is the road to bankruptcy, smart contract vanity is the road to success."
Research from UCSB identified 50,081 addresses as decentralized exchanges through arbitrage detection. Attribution was done by:
This research revealed 180 unique Uniswap v2 factories, demonstrating how vanity addresses can be used for exchange identification (McLaughlin et al. 3299).
Address poisoning aims to create a vanity address resembling a legitimate wallet that the target frequently interacts with. The attacker then:
Typically, the first 4-6 characters and last 4-6 characters are made to resemble the target address.
Example (FAKE illustrative addresses):
0xABCD1234ABCD1234ABCD1234ABCD1234ABCD12340xABCD5678000056780000567800005678ABCD5678(Notice first 4 and last 4 characters match)
#### Fake Token Transfers
Attackers send fake tokens (e.g., fake USDT) to wallets with addresses similar to ones that received legitimate tokens. These fake token contracts may:
#### Zero Value Spam
With tokens like USDT, transferring 0 amount records on the ledger. Scammers:
A victim lost 1,155 WBTC (~$72M) by copying the wrong address from their transaction history. Remarkably, the stolen funds were eventually returned to the victim (CertiK).
Research from McLaughlin et al. at UCSB conducted a 28-month study (February 2020 - July 2022) analyzing the Ethereum arbitrage ecosystem. Key findings relevant to vanity addresses:
#### Exchange Identification
#### Arbitrage Statistics
#### Exchange Distribution (by frequency in arbitrage)
| Exchange | Usage % |
|---|---|
| Uniswap v2 | 44.9% |
| Uniswap v3 | 15.2% |
| Sushi Swap | 13.5% |
| Balancer v1 | 10.8% |
| Unknown | 5.4% |
#### Arbitrage Cycle Properties
The research identified threats to consensus stability:
Research from Soska et al. at Carnegie Mellon University provides context on how vanity addresses are used in the broader cryptocurrency trading ecosystem.
#### Platform Characteristics
#### Vanity Address Usage BitMEX uses vanity addresses for customer deposit accounts:
3BMEX prefix for all customer addresses#### Clustering Analysis The researchers developed methods to cluster BitMEX accounts:
Results showed sophisticated traders operating multiple accounts:
Analysis of vanity address clusters revealed:
DO NOT use vanity generators for EOAs. The only truly reliable method is to generate addresses yourself with cryptographically secure randomness. Even then, vulnerabilities in generation software can compromise security.
Smart contract vanity addresses are safe when using CREATE2:
To fix the Profanity vulnerability, proper seeding is required:
// Instead of single 4-byte seed
// Use 624-word seed for mt19937
// Or use cryptographically secure RNGThe fix requires feeding a random seed sequence of at least 32 bits to ensure mt19937 produces cryptographically secure outputs (James).
0xba5ed... ("based")Using create2crunch on vast.ai (RTX 3090):
# Install
sudo apt install build-essential -y
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
git clone https://github.com/0age/create2crunch && cd create2crunch
sed -i 's/0x4/0x40/g' src/lib.rs
# Run search
export FACTORY="0xYourFactoryAddress"
export CALLER="0xYourCallerAddress"
export INIT_CODE_HASH="0xYourInitCodeHash"
export LEADING=5
export TOTAL=7
cargo run --release $FACTORY $CALLER $INIT_CODE_HASH 0 $LEADING $TOTALThe CREATE2 Factory uses ENS founder Nick Johnson's "keyless transaction" approach:
This creates a single-use EOA that can only ever deploy one transaction.
CertiK. "Vanity Address and Address Poisoning." CertiK Resources, 29 July 2024, www.certik.com/resources/blog/vanity-address-and-address-poisoning.
foobar. "Vanity Addresses: The Only Safe Way to Do Permissionless Multichain Deployments." 0xfoobar Substack, 10 Jan. 2023, 0xfoobar.substack.com/p/vanity-addresses.
Garreau, Marc. "Web3.py Patterns: Address Mining." Snake Charmers (Ethereum Foundation), 4 Oct. 2021, snakecharmers.ethereum.org/web3-py-patterns-address-mining/.
James. "Fixing Other People's Code." Oregon State University Blogs, 6 Feb. 2023, blogs.oregonstate.edu/james/2023/02/.
McLaughlin, Robert, et al. "A Large Scale Study of the Ethereum Arbitrage Ecosystem." 32nd USENIX Security Symposium, 9-11 Aug. 2023, Anaheim, CA, USA, pp. 3295-3312. USENIX Association, www.usenix.org/conference/usenixsecurity23/presentation/mclaughlin.
Soska, Kyle, et al. "Towards Understanding Cryptocurrency Derivatives: A Case Study of BitMEX." Proceedings of the Web Conference 2021 (WWW '21), 19-23 Apr. 2021, Ljubljana, Slovenia. ACM, New York, NY, USA, doi.org/10.1145/3442381.3450059.
Document compiled: January 2026
For the ethereum-wallet-toolkit project
DISCLAIMER: FOR EDUCATIONAL PURPOSES ONLY
>
This software is provided for educational and research purposes only. DO NOT USE WITH REAL FUNDS. The author accepts NO LIABILITY for any damages. All example addresses and keys shown are FAKE - never use them for real funds.
This document covers the keystore encryption and decryption features of the Ethereum Wallet Toolkit.
Keystores are encrypted JSON files that securely store private keys. They follow the Web3 Secret Storage Definition (Version 3) and are compatible with all major Ethereum wallets including MetaMask, Geth, and MyEtherWallet.
The toolkit supports two KDF algorithms:
| KDF | Security | Speed | Recommended |
|---|---|---|---|
| scrypt | Higher | Slower | Yes (default) |
| pbkdf2 | Good | Faster | For compatibility |
scrypt (default):
pbkdf2:
# Encrypt a private key
python keystore.py encrypt --key 0x... --password mypassword --output wallet.json
# Encrypt with secure password prompt
python keystore.py encrypt --key 0x... --output wallet.json
# Use PBKDF2 instead of scrypt
python keystore.py encrypt --key 0x... --password secret --kdf pbkdf2 --output wallet.json
# Decrypt a keystore
python keystore.py decrypt --file wallet.json --password mypassword
# Decrypt with password prompt (more secure)
python keystore.py decrypt --file wallet.json
# View keystore info without decryption
python keystore.py info --file wallet.json
# Change keystore password
python keystore.py change-password --file wallet.json# Encrypt
python eth_toolkit.py keystore --encrypt --key 0x... --password secret --output wallet.json
# Decrypt
python eth_toolkit.py keystore --decrypt --file wallet.json --password secret{
"version": 3,
"id": "uuid-here",
"address": "abcdefghijkabcdefghijklmnopqrstuvwxyz",
"crypto": {
"ciphertext": "encrypted-private-key",
"cipherparams": {
"iv": "initialization-vector"
},
"cipher": "aes-128-ctr",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"salt": "random-salt",
"n": 262144,
"r": 8,
"p": 1
},
"mac": "message-authentication-code"
}
}from keystore import (
encrypt_keystore,
decrypt_keystore,
save_keystore,
load_keystore,
get_keystore_info
)
# Encrypt a private key
keystore = encrypt_keystore(
private_key='0x...',
password='my-secure-password',
kdf='scrypt'
)
# Save to file
filepath = save_keystore(keystore, 'my-wallet.json')
# Load from file
keystore = load_keystore('my-wallet.json')
# Get info without decryption
info = get_keystore_info(keystore)
print(f"Address: {info['address']}")
print(f"KDF: {info['kdf']}")
# Decrypt
private_key = decrypt_keystore(keystore, 'my-secure-password')--password# Good - password prompted securely
python keystore.py encrypt --key 0x... --output wallet.json
# Avoid - password visible in shell history
python keystore.py encrypt --key 0x... --password secret --output wallet.jsonAlways test decryption after creating a keystore:
# Create keystore
python keystore.py encrypt --key 0x... --output test.json
# Verify decryption works
python keystore.py decrypt --file test.json
# Check the derived address matches expected
python keystore.py info --file test.jsonMetaMask can import keystore files directly:
geth account import --datadir /path/to/data wallet.jsonfrom web3 import Web3
with open('wallet.json', 'r') as f:
keystore = f.read()
private_key = Web3().eth.account.decrypt(keystore, 'password')Incorrect Password
Error: Failed to decrypt keystore - MAC mismatch
Check that the password is correct.Corrupted File
Error: Invalid keystore formatWrong KDF Parameters
Error: Unsupported KDF algorithmIf you forget your password:
DISCLAIMER: FOR EDUCATIONAL PURPOSES ONLY
>
This software is provided for educational and research purposes only. DO NOT USE WITH REAL FUNDS. The author accepts NO LIABILITY for any damages. All example addresses and keys shown are FAKE - never use them for real funds.
This document covers the EIP-712 typed structured data signing features of the Ethereum Wallet Toolkit.
EIP-712 defines a standard for hashing and signing typed structured data. It's widely used in DeFi for:
Every EIP-712 message has four components:
{
"types": {
"EIP712Domain": [...],
"PrimaryType": [...]
},
"primaryType": "PrimaryType",
"domain": {...},
"message": {...}
}Defines the structure of all types used:
{
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"}
],
"Permit": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "deadline", "type": "uint256"}
]
}
}The main type being signed:
{
"primaryType": "Permit"
}Context that binds the signature to a specific contract:
{
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 1,
"verifyingContract": "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
}
}The actual data being signed:
{
"message": {
"owner": "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"spender": "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
"value": "1000000000000000000",
"nonce": 0,
"deadline": 1893456000
}
}# ERC-20 Permit example
python typed_data.py example --type permit --output permit.json
# DEX Order example
python typed_data.py example --type order --output order.json
# Mail example (EIP-712 spec)
python typed_data.py example --type mail --output mail.json# Sign a permit
python typed_data.py sign --file permit.json --key 0xaaa...
# Sign with verbose output
python typed_data.py sign --file order.json --key 0xaaa... --verbose
# Save signature to file
python typed_data.py sign --file permit.json --key 0xaaa... --output signed.json# Verify a signature
python typed_data.py verify \
--file permit.json \
--signature 0xabc... \
--address 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA# Get the signing hash (for debugging)
python typed_data.py hash --file permit.jsonfrom typed_data import (
sign_typed_data,
verify_typed_data,
hash_typed_data,
load_typed_data,
EXAMPLES
)
# Load typed data from file
typed_data = load_typed_data('permit.json')
# Or use built-in examples
typed_data = EXAMPLES['permit']
# Sign
result = sign_typed_data(typed_data, '0xaaa...')
print(f"Signature: {result['signature']}")
print(f"v: {result['v']}, r: {result['r']}, s: {result['s']}")
# Verify
verification = verify_typed_data(
typed_data,
result['signature'],
'0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
)
print(f"Valid: {verification['is_valid']}")
# Calculate hash
hash_value = hash_typed_data(typed_data)
print(f"Hash: {hash_value}")Instead of calling approve() (which costs gas), users sign a permit off-chain:
{
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"}
],
"Permit": [
{"name": "owner", "type": "address"},
{"name": "spender", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "deadline", "type": "uint256"}
]
},
"primaryType": "Permit",
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 1,
"verifyingContract": "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
},
"message": {
"owner": "0xYourAddress",
"spender": "0xSpenderContract",
"value": "1000000000",
"nonce": 0,
"deadline": 1893456000
}
}Off-chain limit orders for decentralized exchanges:
{
"types": {
"EIP712Domain": [...],
"Order": [
{"name": "maker", "type": "address"},
{"name": "taker", "type": "address"},
{"name": "makerToken", "type": "address"},
{"name": "takerToken", "type": "address"},
{"name": "makerAmount", "type": "uint256"},
{"name": "takerAmount", "type": "uint256"},
{"name": "expiry", "type": "uint256"},
{"name": "salt", "type": "uint256"}
]
},
"primaryType": "Order",
"domain": {
"name": "Exchange",
"version": "1.0",
"chainId": 1,
"verifyingContract": "0xExchangeContract"
},
"message": {
"maker": "0xYourAddress",
"taker": "0x0000000000000000000000000000000000000000",
"makerToken": "0xWETH",
"takerToken": "0xUSDC",
"makerAmount": "1000000000000000000",
"takerAmount": "3000000000",
"expiry": 1893456000,
"salt": 12345
}
}Allow relayers to pay gas on behalf of users:
{
"types": {
"EIP712Domain": [...],
"ForwardRequest": [
{"name": "from", "type": "address"},
{"name": "to", "type": "address"},
{"name": "value", "type": "uint256"},
{"name": "gas", "type": "uint256"},
{"name": "nonce", "type": "uint256"},
{"name": "data", "type": "bytes"}
]
},
"primaryType": "ForwardRequest",
"message": {
"from": "0xUserAddress",
"to": "0xTargetContract",
"value": "0",
"gas": "100000",
"nonce": 0,
"data": "0x..."
}
}| Solidity Type | EIP-712 Type |
|---|---|
| address | address |
| bool | bool |
| uint256 | uint256 |
| int256 | int256 |
| bytes32 | bytes32 |
| bytes | bytes |
| string | string |
| Custom struct | Custom type |
| Array | Type[] |
The domain separator prevents signature replay across:
domainSeparator = keccak256(
encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(name),
keccak256(version),
chainId,
verifyingContract
)
)Always verify the domain matches the intended contract:
verifyingContract is the correct addresschainId matches your networkname and version match the contractBefore signing:
EIP-712 prevents cross-domain replay, but:
Malicious dApps may request signatures that:
Always review what you're signing!
function verify(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
bytes32 structHash = keccak256(abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonces[owner]++,
deadline
));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ECDSA.recover(hash, v, r, s);
require(signer == owner, "Invalid signature");
require(block.timestamp <= deadline, "Expired");
}import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract MyContract is EIP712 {
constructor() EIP712("MyContract", "1") {}
function _verify(bytes memory signature, bytes32 hash, address signer)
internal view returns (bool)
{
return ECDSA.recover(_hashTypedDataV4(hash), signature) == signer;
}
}If signatures fail to verify:
# Calculate hash for comparison
python typed_data.py hash --file permit.json
# Compare with contract's domain separator
cast call 0xContract "DOMAIN_SEPARATOR()(bytes32)"DISCLAIMER: FOR EDUCATIONAL PURPOSES ONLY
>
This software is provided for educational and research purposes only. DO NOT USE WITH REAL FUNDS. The author accepts NO LIABILITY for any damages. All example addresses shown are FAKE illustrative examples.
This document outlines security best practices for using the Ethereum Wallet Toolkit.
The toolkit uses Python's secrets module and the operating system's cryptographically secure pseudorandom number generator (CSPRNG) for all key generation:
/dev/urandomCryptGenRandomThe eth-account library handles entropy generation internally using these secure sources.
BIP39 Mnemonic:
BIP32 HD Wallet:
For maximum security when generating wallets:
# Create isolated virtual environment
python -m venv --copies venv
source venv/bin/activate
# Verify no network-capable packages are installed
pip list
# Run the toolkit
python eth_toolkit.py generate --mnemonic| Storage Method | Security Level | Use Case |
|---|---|---|
| Hardware wallet | Highest | Large holdings |
| Encrypted USB | High | Cold storage |
| Paper wallet | High | Long-term backup |
| Password manager | Medium | Day-to-day access |
| Plain text file | Dangerous | Never recommended |
# Generate wallet and save to encrypted file
python eth_toolkit.py generate --mnemonic --output wallet.json
# The output file should be:
# 1. Encrypted with a strong password
# 2. Stored on encrypted storage
# 3. Backed up securely
# 4. Deleted from unencrypted storageThe open-source vanity address generator "Profanity" contained a critical flaw that led to $160 million in losses for Wintermute and other victims.
Root Cause: Profanity used mt19937 pseudo-random number generator with only a 4-byte seed. Since Ethereum private keys are 32 bytes, this reduced the keyspace from 2^256 to 2^32 possible keys—easily brute-forceable.
How Attackers Exploited It:
This toolkit avoids the specific Profanity vulnerability by:
eth-account which relies on OS CSPRNG (/dev/urandom on Linux, CryptGenRandom on Windows)However, this does NOT guarantee safety. Other vulnerabilities may exist. This toolkit is for EDUCATIONAL PURPOSES ONLY. Always audit the code yourself and use at your own risk.
Reference: James. "Fixing Other People's Code." Oregon State University Blogs, February 2023.
Address poisoning is a social engineering attack where attackers:
Example (FAKE illustrative addresses):
0xABCD1234ABCD1234ABCD1234ABCD1234ABCD12340xABCD5678000056780000567800005678ABCD5678(Notice the first 4 and last 4 characters match, middle is different)
Notable Incident: Address poisoning attacks have resulted in millions of dollars in losses.
Protection:
Reference: CertiK. "Vanity Address and Address Poisoning." CertiK Resources, July 2024.
| Threat | Mitigation |
|---|---|
| Weak RNG | Uses OS CSPRNG via eth-account |
| Network interception | Run offline |
| Malicious dependencies | Uses only official Ethereum Foundation libs |
| Memory inspection | User responsibility (air-gapped system) |
| Supply chain attack | Verify source code before use |
| Profanity-style vulnerability | Fresh CSPRNG entropy per generation |
| Threat | User Responsibility |
|---|---|
| Compromised OS | Use trusted operating system |
| Hardware keyloggers | Verify physical security |
| Shoulder surfing | Ensure private environment |
| Social engineering | Verify all instructions |
| Malware on system | Run on clean system |
1. Compromised Random Number Generator
2. Modified Source Code
3. Dependency Confusion
Before using this toolkit for valuable assets, review:
Account.create() is used for random generationAccount.create_with_mnemonic() for mnemonic generation# Verify eth-account is official
pip show eth-account
# Check homepage: https://github.com/ethereum/eth-account
# Verify package integrity
pip hash eth-account
# Count lines of code (should be ~600-800)
wc -l eth_toolkit.py vanity.py
# Search for suspicious patterns
grep -r "http\|https\|request\|socket\|urllib" *.pyIf you discover a security vulnerability:
This toolkit is provided as-is for educational and personal use. The authors are not responsible for:
Always verify generated addresses before depositing significant funds.
""" Documentation Resources for Ethereum Wallet MCP Server
This module implements MCP resources providing documentation:
Resources provide static content that can be read by MCP clients. """
from mcp.server import Server from mcp.types import Resource, TextContent
SUPPORTED_LANGUAGES = [ "english", "spanish", "french", "italian", "japanese", "korean", "chinese_simplified", "chinese_traditional", "czech", "portuguese" ]
def register_documentation_resources(server: Server) -> None: """ Register all documentation resources with the MCP server.
Args: server: MCP Server instance to register resources with """
@server.resource("wallet://documentation/bip39") async def get_bip39_documentation() -> str: """ Resource providing BIP39 standard documentation.
Returns comprehensive documentation about the BIP39 mnemonic standard used for wallet seed phrases. """ return BIP39_DOCUMENTATION
@server.resource("wallet://documentation/derivation-paths") async def get_derivation_paths_documentation() -> str: """ Resource providing HD derivation path documentation.
Returns documentation about BIP44 standard paths and Ethereum-specific derivation conventions. """ return DERIVATION_PATHS_DOCUMENTATION
@server.resource("wallet://wordlist/{language}") async def get_wordlist(language: str) -> str: """ Resource providing BIP39 wordlists by language.
Args: language: Language code (english, spanish, french, etc.)
Returns: Complete BIP39 wordlist for the specified language """ language = language.lower().strip()
if language not in SUPPORTED_LANGUAGES: return f"Error: Unsupported language '{language}'. Supported: {', '.join(SUPPORTED_LANGUAGES)}"
try: from mnemonic import Mnemonic mnemo = Mnemonic(language) words = mnemo.wordlist
header = f"# BIP39 Wordlist: {language.title()}\n\n" header += f"Total words: {len(words)}\n\n" header += "---\n\n"
word_list = "\n".join(f"{i+1}. {word}" for i, word in enumerate(words))
return header + word_list
except Exception as e: return f"Error loading wordlist for '{language}': {str(e)}"
BIP39_DOCUMENTATION = """# BIP39: Mnemonic Code for Generating Deterministic Keys
BIP39 (Bitcoin Improvement Proposal 39) describes the implementation of a mnemonic code or mnemonic sentence -- a group of easy-to-remember words -- for the generation of deterministic wallets.
This standard is widely adopted across the cryptocurrency ecosystem and is the foundation for most modern wallet backup and recovery systems.
The process begins with generating random entropy:
| Word Count | Entropy Bits | Checksum Bits | Total Bits |
|---|---|---|---|
| 12 words | 128 bits | 4 bits | 132 bits |
| 15 words | 160 bits | 5 bits | 165 bits |
| 18 words | 192 bits | 6 bits | 198 bits |
| 21 words | 224 bits | 7 bits | 231 bits |
| 24 words | 256 bits | 8 bits | 264 bits |
A checksum is computed by taking the first entropy_bits / 32 bits of the SHA256 hash of the entropy. This checksum is appended to the entropy.
The combined entropy + checksum is split into 11-bit groups. Each 11-bit value (0-2047) maps to a word in the 2048-word BIP39 wordlist.
The mnemonic is converted to a 512-bit seed using PBKDF2-HMAC-SHA512:
The security of your wallet depends entirely on the quality of entropy used:
Never create your own mnemonic by picking words! The checksum will be invalid, and human-chosen words are not random.
| Use Case | Recommended | Security Level |
|---|---|---|
| Testing/Development | 12 words | 128 bits |
| Personal Use | 12-24 words | 128-256 bits |
| High Value | 24 words | 256 bits |
| Institutional | 24 words + passphrase | 256+ bits |
BIP39 supports an optional passphrase that provides:
Warning: Forgetting the passphrase means permanent loss of funds!
BIP39 defines wordlists in multiple languages:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.