This is the fifth part of the Find My Phrase series in finding a seed phrase (with code!):
- Part 1: Find the last word in a seed phrase.
- Part 2: Find any word in a seed phrase.
- Part 3: Find a used seed phrase.
- Part 4: Find multiple missing words in a seed phrase.
- Part 5: Find a used seed phrase with multiple missing words. ← We're here
We're now able to get a list of potential seed phrases when one or more word(s) are missing (from Part 4).
AND we're also able to check if there is a seed phrase that has been used in a list of seed phrases (from Part 3).
So now we can put these two together...to find a used seed phrase from a list of potential seed phrases when one or more words are missing.
Remember, there is a limit to how many words you can have missing (in terms of seed phrases you can realistically check).
The more words you have missing, the more time and computing power required.
But if you're missing one or two words, you'll be able to find your seed phrase if you used it (i.e. sent or received BTC at some point in time).
Disclaimer: This is meant to be an educational exercise to utilize programming to explore automation. It is not recommend to do this with your own seed phrase without a secure machine. Entering your seed phrase on a device connected to the internet exposes your seed phrase to potential security threats. If you choose to do so, you fully understand the risks are liable for the consequences.
Finding a Used Seed Phrase with Missing Multiple Words
Step 1: Level Set
You should have three files in your folder now:
Step 2: Importing Phrase Check
Open findmyphrase.py.
We're going to import our phrase checking function by adding import phrase_check to where we import our libraries.
It should look like this at the beginning.
import itertools
import hashlib
import phrase_check
And thats it. We're done.
Putting it Together
As a reminder your two .py files should look like this now:
findmyphrase.py
import itertools
import hashlib
import phrase_check
def get_possible(seed_phrase):
#converts seed phrase into a list to be able to interface with each word individually.
seed_phrase = seed_phrase.split(" ")
if len(seed_phrase) not in [12, 15, 18, 21, 24]:
print("Your seed phrase must be 12, 15, 18, 21, or 24 words. Please place a question mark (?) for missing words.")
raise SystemExit(0)
#opens the "english.txt" file and stores it into variable "english"
english = open("english.txt")
#reads the "english.txt" file stored in variable "english" and stores the words in the variable "word_list". Also, changes the variable type to a list.
word_list = english.read().split("\n")
#closes the "english.txt" file stored in variable "english" since we don't need it anymore.
english.close()
#converts seed_phrase (with words) to indexed number in BIP39 wordlist
seed_phrase_index = [word_list.index(word) if word != "?" else word for word in seed_phrase]
#converts seed_phrase_index (with numbers) to binary
seed_phrase_binary = [format(number, "011b") if number != "?" else number for number in seed_phrase_index]
#calculates the number of missing bits based on length of seed phrase
num_missing_bits = int(11-(1/3)*(len(seed_phrase)))
#calculates all the possible bits for a missing word
possible_word_bits = (bin(x)[2:].rjust(11, "0") for x in range(2**11))
if seed_phrase_binary[-1] != "?": #if the last word is not "?"
missing_bits_possible = (seed_phrase_binary[-1][0:num_missing_bits],) #save the leftover bits
checksum = seed_phrase_binary[-1][-(11-num_missing_bits):] #save the checksum
else:
#calculates all the possible permutation of missing bits for entropy
missing_bits_possible = (bin(x)[2:].rjust(num_missing_bits, "0") for x in range(2**num_missing_bits)) # calculate all the possible leftover bits
checksum = "" #empty checksum
#determine all the possible bit "combinations" (cartesian product) depending on the number of missing words
possible_word_bits_combination = (combination for combination in itertools.product(possible_word_bits,repeat=seed_phrase[:-1].count("?")))
#input all the "combinations" into the the binary form of the seed phrase (minus the last word), also known as your partial entropy
partial_entropy = tuple("".join((combination.pop(0) if word == "?" else seed_phrase_local[index] for index,word in enumerate(seed_phrase_local))) if (seed_phrase_local := seed_phrase_binary[:-1]) and (combination := list(word_bits_combination)) else "".join(seed_phrase_local) for word_bits_combination in possible_word_bits_combination)
#adds the missing bits to complete the entropy
entropy_possible = tuple(bit_combination + missing_bits for missing_bits in missing_bits_possible for bit_combination in partial_entropy )
#input each entropy_possible in the SHA256 function to result in the corresponding checksum
seed_phrase_binary_possible = (entropy + calc_checksum for entropy in entropy_possible if checksum == (calc_checksum := format(hashlib.sha256(int(entropy, 2).to_bytes(len(entropy) // 8, byteorder="big")).digest()[0],"08b")[:11-num_missing_bits]) or checksum == "")
#transforms all of the seed phrases in binary form back to word form
seed_phrase_possible = tuple(" ".join([word_list[int(binary[i:i+11],2)] for i in range(0, len(binary), 11)]) for binary in seed_phrase_binary_possible)
return seed_phrase_possible
phrase_check.py
from pycoin.symbols.btc import network
import requests
import hashlib
def calc_key(seed_phrase , passphrase):
seed = hashlib.pbkdf2_hmac("sha512",
seed_phrase.encode("utf-8"),
salt=("mnemonic" + passphrase).encode("utf-8"),
iterations=2048,
dklen=64)
master_key = network.keys.bip32_seed(seed)
return master_key
def gen_address(derivation_path, master_key):
subkey = master_key.subkey_for_path(derivation_path)
hash_160 = subkey.hash160(is_compressed=True)
if derivation_path[:2] == "49":
script = network.contract.for_p2pkh_wit(hash_160)
address = network.address.for_p2s(script)
elif derivation_path[:2] == "84":
address = network.address.for_p2pkh_wit(hash_160)
else:
address = subkey.address()
return address
def address_usage(address_list):
address_url = "https://blockchain.info/balance?active="+"|".join(map("|".join, address_list))
address_data = requests.get(address_url)
for key,value in address_data.json().items():
if value['total_received'] > 0:
address = key
print("Address: "+ key)
print("Final Balance: " + str(value["final_balance"]))
print("Total Recieved: " + str(value["total_received"]))
print("Number of Tx: " + str(value["n_tx"]))
break
else:
address = ""
return address
def phrase_usage(seed_phrase_list,
passphrase = "",
derivation_path = ("0/0",
"44'/0'/0'/0/0",
"49'/0'/0'/0/0",
"84'/0'/0'/0/0")):
max_address_limit = 100
seed_phrase_limit = max_address_limit//len(derivation_path)
for i in range(0, len(seed_phrase_list), seed_phrase_limit):
master_keys = [calc_key(seed_phrase, passphrase) for seed_phrase in seed_phrase_list[i:i+seed_phrase_limit]]
addresses = [[gen_address(path, key) for path in derivation_path] for key in master_keys]
address_found = address_usage(addresses)
if address_found != "":
index_match = [i for i, group in enumerate(addresses)if address_found in group][0]
print("Seed Phrase: " + seed_phrase_list[i:i+seed_phrase_limit][index_match])
break
print("COMPLETE")
Step 3: Testing it Out
We're going to test out with this seed phrase: "? ? sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby".
Two words are missing now: the first and the second.
Add this to your code:
seed_phrase = "? ? sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby"
phrase_check.phrase_usage(get_possible(seed_phrase))
Save the code and run it. It might take a few minutes. It may look like nothing is happening for 3-4 minutes but its working.
You should get this result:
Address: 16RCf8jAfz495wTq7umNS8mEv3uNofn6gX Final Balance: 0 Total Recieved: 8586 Number of Tx: 6 Seed Phrase: element entire sniff tired miracle solve shadow scatter hello never tank side sight isolate sister uniform advice pen praise soap lizard festival connect baby COMPLETE
I would not try this with more than 2 words at the moment. It'll take a long while and potentially stall your machine.