Skip to content

Commit d8289c6

Browse files
authored
Add support for 24 word electrumv1 seeds (#571)
1 parent 2fe30ef commit d8289c6

File tree

3 files changed

+35
-7
lines changed

3 files changed

+35
-7
lines changed

btcrecover/btcrseed.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ def create_from_params(cls, mpk = None, addresses = None, address_limit = None,
581581
# Performs basic checks so that clearly invalid mnemonic_ids can be completely skipped
582582
@staticmethod
583583
def verify_mnemonic_syntax(mnemonic_ids):
584-
return len(mnemonic_ids) == 12 and None not in mnemonic_ids
584+
return len(mnemonic_ids) in [12,24] and None not in mnemonic_ids
585585

586586
# This is the time-consuming function executed by worker thread(s). It returns a tuple: if a mnemonic
587587
# is correct return it, else return False for item 0; return a count of mnemonics checked for item 1
@@ -602,7 +602,7 @@ def return_verified_password_or_false(self, mnemonic_ids_list):
602602

603603
# Compute the binary seed from the word list the Electrum1 way
604604
seed = ""
605-
for i in range(0, 12, 3):
605+
for i in range(0, len(mnemonic_ids), 3):
606606
seed += "{:08x}".format( mnemonic_ids[i ]
607607
+ num_words * ( (mnemonic_ids[i + 1] - mnemonic_ids[i ]) % num_words )
608608
+ num_words2 * ( (mnemonic_ids[i + 2] - mnemonic_ids[i + 1]) % num_words ))
@@ -657,7 +657,11 @@ def return_verified_password_or_false(self, mnemonic_ids_list):
657657
# Configures the values of four globals used later in config_btcrecover():
658658
# mnemonic_ids_guess, close_mnemonic_ids, num_inserts, and num_deletes
659659
@classmethod
660-
def config_mnemonic(cls, mnemonic_guess = None, closematch_cutoff = 0.65):
660+
def config_mnemonic(cls, mnemonic_guess = None, closematch_cutoff = 0.65, expected_len = None):
661+
if expected_len:
662+
if expected_len not in [12,24]:
663+
raise ValueError("Electrum1 mnemoincs can only be 12 or 24 words lon")
664+
661665
# If a mnemonic guess wasn't provided, prompt the user for one
662666
if not mnemonic_guess:
663667
init_gui()
@@ -694,9 +698,24 @@ def config_mnemonic(cls, mnemonic_guess = None, closematch_cutoff = 0.65):
694698
" trying all possible seed words here instead.".format(word))
695699
mnemonic_ids_guess += None,
696700

701+
guess_len = len(mnemonic_ids_guess)
702+
if not expected_len:
703+
if guess_len < 12:
704+
expected_len = 12
705+
elif guess_len > 24:
706+
expected_len = 24
707+
else:
708+
off_by = guess_len % 3
709+
if off_by == 0: # If the supplied guess is a valid length, assume that all words have been supplied
710+
expected_len = guess_len
711+
else: # If less words have been supplied, round up to the nearest valid seed length (Assume words are missing by default)
712+
expected_len = guess_len + 3 - off_by
713+
714+
print("Assuming a", expected_len, "word mnemonic. (This can be overridden with --mnemonic-length)")
715+
697716
global num_inserts, num_deletes
698-
num_inserts = max(12 - len(mnemonic_ids_guess), 0)
699-
num_deletes = max(len(mnemonic_ids_guess) - 12, 0)
717+
num_inserts = max(expected_len - len(mnemonic_ids_guess), 0)
718+
num_deletes = max(len(mnemonic_ids_guess) - expected_len, 0)
700719
if num_inserts:
701720
print("Seed sentence was too short, inserting {} word{} into each guess."
702721
.format(num_inserts, "s" if num_inserts > 1 else ""))
@@ -3741,7 +3760,6 @@ def main(argv):
37413760
phase["passwordlist"] = args.seedlist
37423761

37433762
if args.wallet_type == "electrum1":
3744-
args.mnemonic_length = None
37453763
args.language = None
37463764

37473765
if args.language:

btcrecover/test/test_seeds.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,10 +564,14 @@ def test_cardano_icarus_24word_baseaddress_opencl(self):
564564
self.address_tester_cardano_opencl("addr1qx3f4r3qqvynsnvhxrkkycp83v93jg2fqkn7scxnvpe6t99f4evt0tdad8cvsdvenma8t68gfdkyvf3efjzslcn7r4ys72w3qh",
565565
"wood blame garbage one federal jaguar slogan movie thunder seed apology trigger spoon depth basket fine culture boil render special enforce dish middle antique")
566566

567-
def test_electrum1_addr_legacy_BTC(self):
567+
def test_electrum1_addr_legacy_12word_BTC(self):
568568
self.address_tester(btcrseed.WalletElectrum1, "12zAz6pAB6LhzGSZFCc6g9uBSWzwESEsPT", 3,
569569
"straight subject wild ask clean possible age hurt squeeze cost stuck softly")
570570

571+
def test_electrum1_addr_legacy_24_word_BTC(self):
572+
self.address_tester(btcrseed.WalletElectrum1, "1MFu6Wyp6Gy3PDpz2PtoNVdiFWDHR8TMuS", 3,
573+
"bowl especially tomorrow fan sail defeat scary knock ripple third cheek blind join mark rock scratch truth interest bone perfection curve milk taint terror")
574+
571575
def test_electrum2_addr_legacy_BTC(self):
572576
self.address_tester(btcrseed.WalletElectrum2, "14dpd9nayyoyCTNki5UUsm1KnAZ1x7o83E", 5,
573577
"eagle pair eager human cage forget pony fall robot vague later bright acid",

docs/Usage_Examples/basic_seed_recoveries.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,10 @@ python seedrecover.py --wallet-type tron --addrs TLxkYzNpMCEz5KThVuZzoyjde1UfsJK
116116
One missing word, address generation limit of 2. (So address needs to be in the first account)
117117
```
118118
python seedrecover.py --wallet-type elrond --addrs erd16jn439kmwgqj9j0xjnwk2swg0p7j2jrnvpp4p7htc7wypnx27ttqe9l98m --mnemonic "agree process hard hello artefact govern obtain wedding become robust fish bar alcohol about speak unveil mind bike shift latin pole base ugly artefact" --addr-limit 2
119+
```
120+
121+
### Basic Electrum Legacy Recoveries
122+
One wrong word, address generation limit of 2. (So address needs to be in the first account)
123+
```
124+
python seedrecover.py --wallet-type electrum1 --addrs 1Pw1yjF5smzg6eWbE2LbFm7fr1zq7WUYc7 --mnemonic "milk hungry group sound Lift Connect throw rabbit gift leg new lady pie government swear flat dove imagination sometime prepare lot trembl alone bus" --addr-limit 2
119125
```

0 commit comments

Comments
 (0)