Example #1
0
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)
Example #2
0
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)
Example #3
0
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
Example #4
0
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)
Example #5
0
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
Example #6
0
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")
Example #7
0
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
Example #8
0
def get_mnemonic_threshold(mnemonic: str) -> int:
    _, _, _, _, _, _, threshold, _ = slip39.decode_mnemonic(mnemonic)
    return threshold
Example #9
0
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