async def next_menu(self, idx, choice): words = WordNestMenu.words cls = self.__class__ if choice.label[-1] == '-': ch = letter_choices(choice.label[0:-1]) return cls(items=[MenuItem(i, menu=self.next_menu) for i in ch]) # terminal choice, start next word words.append(choice.label) #print(("words[%d]: " % len(words)) + ' '.join(words)) assert len(words) <= self.target_words if len(words) == 23 and self.has_checksum: # we can provide final 8 choices, but only for 24-word case final_words = list(bip39.a2b_words_guess(words)) async def picks_chk_word(s, idx, choice): # they picked final word, the word includes valid checksum bits words.append(choice.label) await cls.done_cb(words.copy()) items = [MenuItem(w, f=picks_chk_word) for w in final_words] items.append(MenuItem('(none above)', f=self.explain_error)) return cls(is_commit=True, items=items) # add a few top-items in certain cases if len(words) == self.target_words: if self.has_checksum: try: bip39.a2b_words(' '.join(words)) correct = True except ValueError: correct = False else: correct = True # they have checksum right, so they are certainly done. if correct: # they are done, don't force them to do any more! return await cls.done_cb(words.copy()) else: # give them a chance to confirm and/or start over return cls(is_commit=True, items=[ MenuItem('(INCORRECT)', f=self.explain_error), MenuItem('(start over)', f=self.start_over) ]) # pop stack to reset depth, and start again at a- .. z- cls.pop_all() return cls(items=None, is_commit=True)
async def all_done(new_words): # So we have another part, might be done or not. global import_xor_parts assert len(new_words) == 24 import_xor_parts.append(new_words) XORWordNestMenu.pop_all() num_parts = len(import_xor_parts) seed = xor32(*(bip39.a2b_words(w) for w in import_xor_parts)) msg = "You've entered %d parts so far.\n\n" % num_parts if num_parts >= 2: chk_word = bip39.b2a_words(seed).split(' ')[-1] msg += "If you stop now, the 24th word of the XOR-combined seed phrase\nwill be:\n\n" msg += "24: %s\n\n" % chk_word if all((not x) for x in seed): # zero seeds are never right. msg += "ZERO WARNING\nProvided seed works out to all zeros "\ "right now. You may have doubled a part or made some other mistake.\n\n" msg += "Press (1) to enter next list of words, or (2) if done with all words." ch = await ux_show_story(msg, strict_escape=True, escape='12x', sensitive=True) if ch == 'x': # give up import_xor_parts.clear() # concern: we are contaminated w/ secrets return None elif ch == '1': # do another list of words nxt = XORWordNestMenu(num_words=24) the_ux.push(nxt) elif ch == '2': # done; import on temp basis, or be the main secret from pincodes import pa enc = stash.SecretStash.encode(seed_phrase=seed) if pa.is_secret_blank(): # save it since they have no other secret set_seed_value(encoded=enc) # update menu contents now that wallet defined goto_top_menu() else: pa.tmp_secret(enc) await ux_show_story( "New master key in effect until next power down.") return None
def set_seed_value(words=None, encoded=None): # Save the seed words into secure element, and reboot. BIP-39 password # is not set at this point (empty string) if words: bip39.a2b_words(words) # checksum check # map words to bip39 wordlist indices data = [bip39.wordlist_en.index(w) for w in words] # map to packed binary representation. val = 0 for v in data: val <<= 11 val |= v # remove the checksum part vlen = (len(words) * 4) // 3 val >>= (len(words) // 3) # convert to bytes seed = val.to_bytes(vlen, 'big') assert len(seed) == vlen # encode it for our limited secret space nv = SecretStash.encode(seed_phrase=seed) else: nv = encoded from glob import dis dis.fullscreen('Applying...') pa.change(new_secret=nv) # re-read settings since key is now different # - also captures xfp, xpub at this point pa.new_main_secret(nv) # check and reload secret pa.reset() pa.login()
def test_a2b(): for value, words in b39_data.vectors: got = bip39.a2b_words(words) assert got == value
def test_vectors(): for raw, words, ms, _ in b39_vectors.english[0:10]: assert bip39.a2b_words(words) == a2b_hex(raw) got = bip39.master_secret(words.encode('utf'), a2b_hex('5452455a4f52')) assert got == a2b_hex(ms)