Skip to content

Instantly share code, notes, and snippets.

@atx
Last active January 24, 2026 14:38
Show Gist options
  • Select an option

  • Save atx/6cca74e88209a1c19e9344dd1d118223 to your computer and use it in GitHub Desktop.

Select an option

Save atx/6cca74e88209a1c19e9344dd1d118223 to your computer and use it in GitHub Desktop.
This is a tool that ingests legacy Parity brain wallet seeds (not-BIP39-compatible) and outputs a wallet.json file that can be imported for example into MetaMask. 100% vibe coded, worked for my wallet but who knows if it is correct...
#!/usr/bin/env python3
"""
Parity Brain Wallet Recovery Tool
Recovers an Ethereum wallet from a Parity brain wallet phrase and outputs
a standard Ethereum JSON keystore file (v3 format).
Algorithm source: parity-ethereum/accounts/ethkey/src/brain.rs
Usage:
python3 recover_parity_wallet.py "phrase words here" "password" [--output wallet.json]
"""
import argparse
import json
import os
import sys
import uuid
from dataclasses import dataclass
from hashlib import pbkdf2_hmac
from Crypto.Cipher import AES
from Crypto.Hash import keccak
# SECP256K1 curve order
SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
def keccak256(data: bytes) -> bytes:
"""Compute Keccak-256 hash."""
k = keccak.new(digest_bits=256)
k.update(data)
return k.digest()
def is_valid_secp256k1_secret(secret: bytes) -> bool:
"""Check if bytes represent a valid SECP256K1 private key."""
secret_int = int.from_bytes(secret, 'big')
# Must be in range [1, order-1]
return 0 < secret_int < SECP256K1_ORDER
def derive_public_key(private_key: bytes) -> bytes:
"""Derive uncompressed public key from private key using SECP256K1."""
# Using ecdsa library for SECP256K1 operations
from ecdsa import SigningKey, SECP256k1
sk = SigningKey.from_string(private_key, curve=SECP256k1)
vk = sk.get_verifying_key()
# Return uncompressed public key (64 bytes, without 0x04 prefix)
return vk.to_string()
def derive_address(public_key: bytes) -> bytes:
"""Derive Ethereum address from public key."""
# Address is last 20 bytes of keccak256(public_key)
return keccak256(public_key)[-20:]
@dataclass
class Keypair:
private_key: bytes
public_key: bytes
address: bytes
def derive_keypair_from_phrase(phrase: str) -> Keypair:
"""
Derive keypair from Parity brain wallet phrase.
Algorithm from parity-ethereum/accounts/ethkey/src/brain.rs:
1. Initial hash: secret = keccak256(phrase.encode('utf-8'))
2. Loop: secret = keccak256(secret)
3. After i > 16384, check:
- is_valid_secp256k1_secret(secret)
- address[0] == 0x00 (first byte must be zero)
4. Return first valid keypair
"""
# Initial hash
secret = keccak256(phrase.encode('utf-8'))
i = 0
while True:
secret = keccak256(secret)
if i <= 16384:
i += 1
else:
# After 16385 iterations, try to create keypair
if is_valid_secp256k1_secret(secret):
public_key = derive_public_key(secret)
address = derive_address(public_key)
# Parity vanity constraint: first byte must be 0x00
if address[0] == 0x00:
return Keypair(
private_key=secret,
public_key=public_key,
address=address
)
def create_keystore(private_key: bytes, address: bytes, password: str) -> dict:
"""
Create Ethereum JSON keystore v3 format.
Uses PBKDF2 for key derivation and AES-128-CTR for encryption.
"""
# Generate random salt and IV
salt = os.urandom(32)
iv = os.urandom(16)
# PBKDF2 key derivation (matches Parity's default: 10240 iterations)
derived_key = pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt,
iterations=10240,
dklen=32
)
# AES-128-CTR encryption
# Use first 16 bytes of derived key for encryption
cipher = AES.new(derived_key[:16], AES.MODE_CTR, nonce=b'', initial_value=iv)
ciphertext = cipher.encrypt(private_key)
# MAC = keccak256(derived_key[16:32] + ciphertext)
mac = keccak256(derived_key[16:32] + ciphertext)
return {
"address": address.hex(),
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": {
"iv": iv.hex()
},
"ciphertext": ciphertext.hex(),
"kdf": "pbkdf2",
"kdfparams": {
"c": 10240,
"dklen": 32,
"prf": "hmac-sha256",
"salt": salt.hex()
},
"mac": mac.hex()
},
"id": str(uuid.uuid4()),
"version": 3
}
def decrypt_keystore(keystore: dict, password: str) -> bytes:
"""Decrypt a keystore to verify it works correctly."""
crypto = keystore['crypto']
salt = bytes.fromhex(crypto['kdfparams']['salt'])
iv = bytes.fromhex(crypto['cipherparams']['iv'])
ciphertext = bytes.fromhex(crypto['ciphertext'])
expected_mac = bytes.fromhex(crypto['mac'])
# Derive key
derived_key = pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt,
iterations=crypto['kdfparams']['c'],
dklen=crypto['kdfparams']['dklen']
)
# Verify MAC
computed_mac = keccak256(derived_key[16:32] + ciphertext)
if computed_mac != expected_mac:
raise ValueError("MAC verification failed - wrong password or corrupted keystore")
# Decrypt
cipher = AES.new(derived_key[:16], AES.MODE_CTR, nonce=b'', initial_value=iv)
return cipher.decrypt(ciphertext)
def main():
parser = argparse.ArgumentParser(
description='Recover Ethereum wallet from Parity brain wallet phrase'
)
parser.add_argument('phrase', help='The brain wallet phrase (space-separated words)')
parser.add_argument('password', help='Password to encrypt the JSON keystore')
parser.add_argument('--output', '-o', help='Output file path (default: stdout)')
parser.add_argument('--verbose', '-v', action='store_true', help='Show derived address')
parser.add_argument('--verify', action='store_true', help='Verify keystore can be decrypted')
args = parser.parse_args()
# Derive keypair
if args.verbose:
print(f"Deriving keypair from phrase...", file=sys.stderr)
keypair = derive_keypair_from_phrase(args.phrase)
if args.verbose:
print(f"Secret: {keypair.private_key.hex()}", file=sys.stderr)
print(f"Address: 0x{keypair.address.hex()}", file=sys.stderr)
# Create keystore
keystore = create_keystore(keypair.private_key, keypair.address, args.password)
# Verify keystore if requested
if args.verify:
decrypted = decrypt_keystore(keystore, args.password)
if decrypted != keypair.private_key:
print("ERROR: Keystore verification failed!", file=sys.stderr)
sys.exit(1)
print("Keystore verification: OK", file=sys.stderr)
# Output
output = json.dumps(keystore, indent=2)
if args.output:
with open(args.output, 'w') as f:
f.write(output)
if args.verbose:
print(f"Keystore written to: {args.output}", file=sys.stderr)
else:
print(output)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment