async def _show_remaining_groups_and_shares(ctx: wire.Context) -> None: """ Show info dialog for Slip39 Advanced - what shares are to be entered. """ shares_remaining = storage.recovery.fetch_slip39_remaining_shares() identifiers = [] first_entered_index = -1 for i in range(len(shares_remaining)): if shares_remaining[i] < slip39.MAX_SHARE_COUNT: first_entered_index = i share = None for index, remaining in enumerate(shares_remaining): if 0 <= remaining < slip39.MAX_SHARE_COUNT: m = storage.recovery_shares.fetch_group(index)[0] if not share: share = slip39.decode_mnemonic(m) identifier = m.split(" ")[0:3] identifiers.append([remaining, identifier]) elif remaining == slip39.MAX_SHARE_COUNT: # no shares yet identifier = storage.recovery_shares.fetch_group( first_entered_index)[0].split(" ")[0:2] try: # we only add the group (two words) identifier once identifiers.index([remaining, identifier]) except ValueError: identifiers.append([remaining, identifier]) return await layout.show_remaining_shares(ctx, identifiers, shares_remaining, share.group_threshold)
async def _show_remaining_groups_and_shares(ctx: wire.GenericContext) -> None: """ Show info dialog for Slip39 Advanced - what shares are to be entered. """ shares_remaining = storage.recovery.fetch_slip39_remaining_shares() # should be stored at this point assert shares_remaining groups = set() first_entered_index = -1 for i in range(len(shares_remaining)): if shares_remaining[i] < slip39.MAX_SHARE_COUNT: first_entered_index = i share = None for index, remaining in enumerate(shares_remaining): if 0 <= remaining < slip39.MAX_SHARE_COUNT: m = storage.recovery_shares.fetch_group(index)[0] if not share: share = slip39.decode_mnemonic(m) identifier = m.split(" ")[0:3] groups.add((remaining, tuple(identifier))) elif remaining == slip39.MAX_SHARE_COUNT: # no shares yet identifier = storage.recovery_shares.fetch_group( first_entered_index)[0].split(" ")[0:2] groups.add((remaining, tuple(identifier))) assert share # share needs to be set return await layout.show_remaining_shares(ctx, groups, shares_remaining, share.group_threshold)
def process_slip39(words: str) -> Tuple[Optional[bytes], slip39.Share]: """ Processes a single mnemonic share. Returns the encrypted master secret (or None if more shares are needed) and the share's group index and member index. """ share = slip39.decode_mnemonic(words) remaining = storage.recovery.fetch_slip39_remaining_shares() # if this is the first share, parse and store metadata if not remaining: storage.recovery.set_slip39_group_count(share.group_count) storage.recovery.set_slip39_iteration_exponent( share.iteration_exponent) storage.recovery.set_slip39_identifier(share.identifier) storage.recovery.set_slip39_remaining_shares(share.threshold - 1, share.group_index) storage.recovery_shares.set(share.index, share.group_index, words) # if share threshold and group threshold are 1 # we can calculate the secret right away if share.threshold == 1 and share.group_threshold == 1: identifier, iteration_exponent, secret, _ = slip39.combine_mnemonics( [words]) return secret, share else: # we need more shares return None, share # These should be checked by UI before so it's a Runtime exception otherwise if share.identifier != storage.recovery.get_slip39_identifier(): raise RuntimeError("Slip39: Share identifiers do not match") if storage.recovery_shares.get(share.index, share.group_index): raise RuntimeError("Slip39: This mnemonic was already entered") remaining_for_share = (storage.recovery.get_slip39_remaining_shares( share.group_index) or share.threshold) storage.recovery.set_slip39_remaining_shares(remaining_for_share - 1, share.group_index) remaining[share.group_index] = remaining_for_share - 1 storage.recovery_shares.set(share.index, share.group_index, words) if remaining.count(0) < share.group_threshold: # we need more shares return None, share if share.group_count > 1: mnemonics = [] for i, r in enumerate(remaining): # if we have multiple groups pass only the ones with threshold reached if r == 0: group = storage.recovery_shares.fetch_group(i) mnemonics.extend(group) else: # in case of slip39 basic we only need the first and only group mnemonics = storage.recovery_shares.fetch_group(0) identifier, iteration_exponent, secret, _ = slip39.combine_mnemonics( mnemonics) return secret, share
def load_slip39_state() -> Slip39State: previous_mnemonics = fetch_previous_mnemonics() if not previous_mnemonics: return None, None # let's get the first mnemonic and decode it to find out the metadata mnemonic = next(p[0] for p in previous_mnemonics if p) share = slip39.decode_mnemonic(mnemonic) word_count = len(mnemonic.split(" ")) return word_count, backup_types.infer_backup_type(True, share)
def process_single(mnemonic: str) -> bytes: """ Receives single mnemonic and processes it. Returns what is then stored in storage or None if more shares are needed. """ identifier, iteration_exponent, _, _, _, index, threshold, value = slip39.decode_mnemonic( mnemonic) # TODO: use better data structure for this if threshold == 1: raise ValueError("Threshold equal to 1 is not allowed.") # if recovery is not in progress already, start it and wait for more mnemonics if not storage.is_slip39_in_progress(): storage.set_slip39_in_progress(True) storage.set_slip39_iteration_exponent(iteration_exponent) storage.set_slip39_identifier(identifier) storage.set_slip39_threshold(threshold) storage.set_slip39_remaining(threshold - 1) storage.set_slip39_words_count(len(mnemonic.split())) storage.set_slip39_mnemonic(index, mnemonic) return None # we need more shares # check identifier and member index of this share against stored values if identifier != storage.get_slip39_identifier(): # TODO: improve UX (tell user) raise ValueError("Share identifiers do not match") if storage.get_slip39_mnemonic(index): # TODO: improve UX (tell user) raise ValueError("This mnemonic was already entered") # append to storage remaining = storage.get_slip39_remaining() - 1 storage.set_slip39_remaining(remaining) storage.set_slip39_mnemonic(index, mnemonic) if remaining != 0: return None # we need more shares # combine shares and return the master secret mnemonics = storage.get_slip39_mnemonics() if len(mnemonics) != threshold: raise ValueError("Some mnemonics are still missing.") _, _, secret = slip39.combine_mnemonics(mnemonics) return secret
async def load_device(ctx, msg): word_count = _validate(msg) is_slip39 = backup_types.is_slip39_word_count(word_count) if not is_slip39 and not msg.skip_checksum and not bip39.check( msg.mnemonics[0]): raise wire.ProcessError("Mnemonic is not valid") await _warn(ctx) if not is_slip39: # BIP-39 secret = msg.mnemonics[0].encode() backup_type = BackupType.Bip39 else: identifier, iteration_exponent, secret = slip39.recover_ems( msg.mnemonics) # this must succeed if the recover_ems call succeeded share = slip39.decode_mnemonic(msg.mnemonics[0]) if share.group_count == 1: backup_type = BackupType.Slip39_Basic elif share.group_count > 1: backup_type = BackupType.Slip39_Advanced else: raise wire.ProcessError("Invalid group count") storage.device.set_slip39_identifier(identifier) storage.device.set_slip39_iteration_exponent(iteration_exponent) storage.device.store_mnemonic_secret( secret, backup_type, needs_backup=msg.needs_backup is True, no_backup=msg.no_backup is True, ) storage.device.set_passphrase_enabled(msg.passphrase_protection) storage.device.set_label(msg.label or "") if msg.pin: config.change_pin(pin_to_int(""), pin_to_int(msg.pin), None, None) return Success(message="Device loaded")
def _process_slip39(words: str) -> Optional[bytes]: """ Receives single mnemonic and processes it. Returns what is then stored in storage or None if more shares are needed. """ identifier, iteration_exponent, _, _, _, index, threshold, value = slip39.decode_mnemonic( words) # TODO: use better data structure for this if threshold == 1: raise ValueError("Threshold equal to 1 is not allowed.") remaining = storage.recovery.get_remaining() # if this is the first share, parse and store metadata if not remaining: storage.recovery.set_slip39_iteration_exponent(iteration_exponent) storage.recovery.set_slip39_identifier(identifier) storage.recovery.set_slip39_threshold(threshold) storage.recovery.set_remaining(threshold - 1) storage.recovery_shares.set(index, words) return None # we need more shares # These should be checked by UI before so it's a Runtime exception otherwise if identifier != storage.recovery.get_slip39_identifier(): raise RuntimeError("Slip39: Share identifiers do not match") if storage.recovery_shares.get(index): raise RuntimeError("Slip39: This mnemonic was already entered") # add mnemonic to storage remaining -= 1 storage.recovery.set_remaining(remaining) storage.recovery_shares.set(index, words) if remaining != 0: return None # we need more shares # combine shares and return the master secret mnemonics = storage.recovery_shares.fetch() identifier, iteration_exponent, secret = slip39.combine_mnemonics( mnemonics) return secret
def get_mnemonic_threshold(mnemonic: str) -> int: _, _, _, _, _, _, threshold, _ = slip39.decode_mnemonic(mnemonic) return threshold
def process_slip39(words: str) -> Optional[bytes, int, int]: """ Receives single mnemonic and processes it. Returns what is then stored in storage or None if more shares are needed. """ identifier, iteration_exponent, group_index, group_threshold, group_count, index, threshold, value = slip39.decode_mnemonic( words) # TODO: use better data structure for this remaining = storage.recovery.fetch_slip39_remaining_shares() index_with_group_offset = index + group_index * _GROUP_STORAGE_OFFSET # if this is the first share, parse and store metadata if not remaining: storage.recovery.set_slip39_group_count(group_count) storage.recovery.set_slip39_group_threshold(group_threshold) storage.recovery.set_slip39_iteration_exponent(iteration_exponent) storage.recovery.set_slip39_identifier(identifier) storage.recovery.set_slip39_threshold(threshold) storage.recovery.set_slip39_remaining_shares(threshold - 1, group_index) storage.recovery_shares.set(index_with_group_offset, words) return None, group_index, index # we need more shares if remaining[group_index] == 0: raise GroupThresholdReachedError() # These should be checked by UI before so it's a Runtime exception otherwise if identifier != storage.recovery.get_slip39_identifier(): raise RuntimeError("Slip39: Share identifiers do not match") if storage.recovery_shares.get(index_with_group_offset): raise RuntimeError("Slip39: This mnemonic was already entered") remaining_for_share = ( storage.recovery.get_slip39_remaining_shares(group_index) or threshold) storage.recovery.set_slip39_remaining_shares(remaining_for_share - 1, group_index) remaining[group_index] = remaining_for_share - 1 storage.recovery_shares.set(index_with_group_offset, words) if remaining.count(0) < group_threshold: return None, group_index, index # we need more shares if len(remaining) > 1: mnemonics = [] for i, r in enumerate(remaining): # if we have multiple groups pass only the ones with threshold reached if r == 0: group = storage.recovery_shares.fetch_group(i) mnemonics.extend(group) else: mnemonics = storage.recovery_shares.fetch() identifier, iteration_exponent, secret = slip39.combine_mnemonics( mnemonics) return secret, group_index, index