mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-18 04:18:10 +00:00
feat(core): Support extendable backup flag in SLIP-39.
This commit is contained in:
parent
c0b3a2c26b
commit
9d0d1b3402
1
core/.changelog.d/+slip39.added
Normal file
1
core/.changelog.d/+slip39.added
Normal file
@ -0,0 +1 @@
|
||||
Support extendable backup flag in SLIP-39.
|
@ -36,6 +36,7 @@ const INITIALIZED: u16 = FLAG_PUBLIC | APP_DEVICE | 0x0013;
|
||||
const SAFETY_CHECK_LEVEL: u16 = APP_DEVICE | 0x0014;
|
||||
const EXPERIMENTAL_FEATURES: u16 = APP_DEVICE | 0x0015;
|
||||
const HIDE_PASSPHRASE_FROM_HOST: u16 = APP_DEVICE | 0x0016;
|
||||
const SLIP39_EXTENDABLE: u16 = APP_DEVICE | 0x0017;
|
||||
|
||||
pub fn get_avatar_len() -> StorageResult<usize> {
|
||||
get_length(HOMESCREEN)
|
||||
|
@ -49,6 +49,7 @@ def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
|
||||
from trezor.crypto import slip39
|
||||
|
||||
identifier = storage_device.get_slip39_identifier()
|
||||
extendable = storage_device.get_slip39_extendable()
|
||||
iteration_exponent = storage_device.get_slip39_iteration_exponent()
|
||||
if identifier is None or iteration_exponent is None:
|
||||
# Identifier or exponent expected but not found
|
||||
@ -58,6 +59,7 @@ def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
|
||||
passphrase.encode(),
|
||||
iteration_exponent,
|
||||
identifier,
|
||||
extendable,
|
||||
render_func,
|
||||
)
|
||||
|
||||
|
@ -49,7 +49,9 @@ async def load_device(msg: LoadDevice) -> Success:
|
||||
secret = msg.mnemonics[0].encode()
|
||||
backup_type = BackupType.Bip39
|
||||
else:
|
||||
identifier, iteration_exponent, secret = slip39.recover_ems(mnemonics)
|
||||
identifier, extendable, iteration_exponent, secret = slip39.recover_ems(
|
||||
mnemonics
|
||||
)
|
||||
|
||||
# this must succeed if the recover_ems call succeeded
|
||||
share = slip39.decode_mnemonic(mnemonics[0])
|
||||
@ -61,6 +63,7 @@ async def load_device(msg: LoadDevice) -> Success:
|
||||
raise ProcessError("Invalid group count")
|
||||
|
||||
storage_device.set_slip39_identifier(identifier)
|
||||
storage_device.set_slip39_extendable(extendable)
|
||||
storage_device.set_slip39_iteration_exponent(iteration_exponent)
|
||||
|
||||
storage_device.store_mnemonic_secret(
|
||||
|
@ -118,10 +118,15 @@ async def _finish_recovery_dry_run(secret: bytes, backup_type: BackupType) -> Su
|
||||
is_slip39 = backup_types.is_slip39_backup_type(backup_type)
|
||||
# Check that the identifier and iteration exponent match as well
|
||||
if is_slip39:
|
||||
if not backup_types.is_extendable_backup_type(backup_type):
|
||||
result &= (
|
||||
storage_device.get_slip39_identifier()
|
||||
== storage_recovery.get_slip39_identifier()
|
||||
)
|
||||
result &= (
|
||||
storage_device.get_slip39_extendable()
|
||||
== storage_recovery.get_slip39_extendable()
|
||||
)
|
||||
result &= (
|
||||
storage_device.get_slip39_iteration_exponent()
|
||||
== storage_recovery.get_slip39_iteration_exponent()
|
||||
@ -149,11 +154,13 @@ async def _finish_recovery(secret: bytes, backup_type: BackupType) -> Success:
|
||||
)
|
||||
if backup_type in (BackupType.Slip39_Basic, BackupType.Slip39_Advanced):
|
||||
identifier = storage_recovery.get_slip39_identifier()
|
||||
extendable = storage_recovery.get_slip39_extendable()
|
||||
exponent = storage_recovery.get_slip39_iteration_exponent()
|
||||
if identifier is None or exponent is None:
|
||||
if identifier is None or extendable is None or exponent is None:
|
||||
# Identifier and exponent need to be stored in storage at this point
|
||||
raise RuntimeError
|
||||
storage_device.set_slip39_identifier(identifier)
|
||||
storage_device.set_slip39_extendable(extendable)
|
||||
storage_device.set_slip39_iteration_exponent(exponent)
|
||||
|
||||
storage_recovery.end_progress()
|
||||
|
@ -41,13 +41,14 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]:
|
||||
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_extendable(share.extendable)
|
||||
storage_recovery.set_slip39_remaining_shares(share.threshold - 1, group_index)
|
||||
storage_recovery_shares.set(share.index, 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:
|
||||
_, _, secret = slip39.recover_ems([words])
|
||||
_, _, _, secret = slip39.recover_ems([words])
|
||||
return secret, share
|
||||
else:
|
||||
# we need more shares
|
||||
@ -85,7 +86,7 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]:
|
||||
# in case of slip39 basic we only need the first and only group
|
||||
mnemonics = storage_recovery_shares.fetch_group(0)
|
||||
|
||||
_, _, secret = slip39.recover_ems(mnemonics)
|
||||
_, _, _, secret = slip39.recover_ems(mnemonics)
|
||||
return secret, share
|
||||
|
||||
|
||||
|
@ -73,6 +73,7 @@ async def reset_device(msg: ResetDevice) -> Success:
|
||||
elif backup_type in (BAK_T_SLIP39_BASIC, BAK_T_SLIP39_ADVANCED):
|
||||
# generate and set SLIP39 parameters
|
||||
storage_device.set_slip39_identifier(slip39.generate_random_identifier())
|
||||
storage_device.set_slip39_extendable(slip39.DEFAULT_EXTENDABLE_FLAG)
|
||||
storage_device.set_slip39_iteration_exponent(slip39.DEFAULT_ITERATION_EXPONENT)
|
||||
else:
|
||||
# Unknown backup type.
|
||||
@ -183,6 +184,7 @@ def _get_slip39_mnemonics(
|
||||
groups: Sequence[tuple[int, int]],
|
||||
):
|
||||
identifier = storage_device.get_slip39_identifier()
|
||||
extendable = storage_device.get_slip39_extendable()
|
||||
iteration_exponent = storage_device.get_slip39_iteration_exponent()
|
||||
if identifier is None or iteration_exponent is None:
|
||||
raise ValueError
|
||||
@ -192,6 +194,7 @@ def _get_slip39_mnemonics(
|
||||
group_threshold,
|
||||
groups,
|
||||
identifier,
|
||||
extendable,
|
||||
iteration_exponent,
|
||||
encrypted_master_secret,
|
||||
)
|
||||
|
@ -35,6 +35,7 @@ INITIALIZED = const(0x13) # bool (0x01 or empty)
|
||||
_SAFETY_CHECK_LEVEL = const(0x14) # int
|
||||
_EXPERIMENTAL_FEATURES = const(0x15) # bool (0x01 or empty)
|
||||
_HIDE_PASSPHRASE_FROM_HOST = const(0x16) # bool (0x01 or empty)
|
||||
_SLIP39_EXTENDABLE = const(0x17) # bool (0x01 or empty)
|
||||
|
||||
SAFETY_CHECK_LEVEL_STRICT : Literal[0] = const(0)
|
||||
SAFETY_CHECK_LEVEL_PROMPT : Literal[1] = const(1)
|
||||
@ -256,10 +257,24 @@ def set_slip39_identifier(identifier: int) -> None:
|
||||
|
||||
|
||||
def get_slip39_identifier() -> int | None:
|
||||
"""The device's actual SLIP-39 identifier used in passphrase derivation."""
|
||||
"""The device's actual SLIP-39 identifier used in legacy passphrase derivation."""
|
||||
return common.get_uint16(_NAMESPACE, _SLIP39_IDENTIFIER)
|
||||
|
||||
|
||||
def set_slip39_extendable(extendable: bool) -> None:
|
||||
"""
|
||||
The device's actual SLIP-39 extendable backup flag.
|
||||
Not to be confused with recovery.extendable, which is stored only during
|
||||
the recovery process and it is copied here upon success.
|
||||
"""
|
||||
common.set_bool(_NAMESPACE, _SLIP39_EXTENDABLE, extendable)
|
||||
|
||||
|
||||
def get_slip39_extendable() -> bool:
|
||||
"""The device's actual SLIP-39 extendable backup flag."""
|
||||
return common.get_bool(_NAMESPACE, _SLIP39_EXTENDABLE)
|
||||
|
||||
|
||||
def set_slip39_iteration_exponent(exponent: int) -> None:
|
||||
"""
|
||||
The device's actual SLIP-39 iteration exponent used in passphrase derivation.
|
||||
|
@ -13,6 +13,7 @@ _SLIP39_IDENTIFIER = const(0x03) # bytes
|
||||
_REMAINING = const(0x05) # int
|
||||
_SLIP39_ITERATION_EXPONENT = const(0x06) # int
|
||||
_SLIP39_GROUP_COUNT = const(0x07) # int
|
||||
_SLIP39_EXTENDABLE = const(0x08) # bool
|
||||
|
||||
# Deprecated Keys:
|
||||
# _WORD_COUNT = const(0x02) # int
|
||||
@ -56,6 +57,16 @@ def get_slip39_identifier() -> int | None:
|
||||
return common.get_uint16(_NAMESPACE, _SLIP39_IDENTIFIER)
|
||||
|
||||
|
||||
def set_slip39_extendable(extendable: bool) -> None:
|
||||
_require_progress()
|
||||
common.set_bool(_NAMESPACE, _SLIP39_EXTENDABLE, extendable)
|
||||
|
||||
|
||||
def get_slip39_extendable() -> bool:
|
||||
_require_progress()
|
||||
return common.get_bool(_NAMESPACE, _SLIP39_EXTENDABLE)
|
||||
|
||||
|
||||
def set_slip39_iteration_exponent(exponent: int) -> None:
|
||||
_require_progress()
|
||||
common.set_uint8(_NAMESPACE, _SLIP39_ITERATION_EXPONENT, exponent)
|
||||
|
@ -67,10 +67,15 @@ def _xor(a: bytes, b: bytes) -> bytes:
|
||||
_ID_LENGTH_BITS = const(15)
|
||||
"""The length of the random identifier in bits."""
|
||||
|
||||
_ITERATION_EXP_LENGTH_BITS = const(5)
|
||||
_EXTENDABLE_FLAG_LENGTH_BITS = const(1)
|
||||
"""The length of the extendable backup flag in bits."""
|
||||
|
||||
_ITERATION_EXP_LENGTH_BITS = const(4)
|
||||
"""The length of the iteration exponent in bits."""
|
||||
|
||||
_ID_EXP_LENGTH_WORDS = _bits_to_words(_ID_LENGTH_BITS + _ITERATION_EXP_LENGTH_BITS)
|
||||
_ID_EXP_LENGTH_WORDS = _bits_to_words(
|
||||
_ID_LENGTH_BITS + _EXTENDABLE_FLAG_LENGTH_BITS + _ITERATION_EXP_LENGTH_BITS
|
||||
)
|
||||
"""The length of the random identifier and iteration exponent in words."""
|
||||
|
||||
_CHECKSUM_LENGTH_WORDS = const(3)
|
||||
@ -79,8 +84,13 @@ _CHECKSUM_LENGTH_WORDS = const(3)
|
||||
_DIGEST_LENGTH_BYTES = const(4)
|
||||
"""The length of the digest of the shared secret in bytes."""
|
||||
|
||||
_CUSTOMIZATION_STRING = b"shamir"
|
||||
"""The customization string used in the RS1024 checksum and in the PBKDF2 salt."""
|
||||
_CUSTOMIZATION_STRING_ORIG = b"shamir"
|
||||
"""The customization string used in the RS1024 checksum and in the PBKDF2 salt for shares
|
||||
_without_ the extendable backup flag."""
|
||||
|
||||
_CUSTOMIZATION_STRING_EXTENDABLE = b"shamir_extendable"
|
||||
"""The customization string used in the RS1024 checksum for shares _with_ the extendable
|
||||
backup flag."""
|
||||
|
||||
_METADATA_LENGTH_WORDS = _ID_EXP_LENGTH_WORDS + 2 + _CHECKSUM_LENGTH_WORDS
|
||||
"""The length of the mnemonic in words without the share value."""
|
||||
@ -111,6 +121,7 @@ MAX_GROUP_COUNT = const(16)
|
||||
"""The maximum number of groups that can be created."""
|
||||
|
||||
DEFAULT_ITERATION_EXPONENT = const(1)
|
||||
DEFAULT_EXTENDABLE_FLAG = True
|
||||
|
||||
|
||||
class Share:
|
||||
@ -121,6 +132,7 @@ class Share:
|
||||
def __init__(
|
||||
self,
|
||||
identifier: int,
|
||||
extendable: bool,
|
||||
iteration_exponent: int,
|
||||
group_index: int,
|
||||
group_threshold: int,
|
||||
@ -130,6 +142,7 @@ class Share:
|
||||
share_value: bytes,
|
||||
):
|
||||
self.identifier = identifier
|
||||
self.extendable = extendable
|
||||
self.iteration_exponent = iteration_exponent
|
||||
self.group_index = group_index
|
||||
self.group_threshold = group_threshold
|
||||
@ -144,6 +157,7 @@ def decrypt(
|
||||
passphrase: bytes,
|
||||
iteration_exponent: int,
|
||||
identifier: int,
|
||||
extendable: bool,
|
||||
progress_callback: Callable[[int, int], None] | None = None,
|
||||
) -> bytes:
|
||||
"""
|
||||
@ -154,7 +168,7 @@ def decrypt(
|
||||
"""
|
||||
l = encrypted_master_secret[: len(encrypted_master_secret) // 2]
|
||||
r = encrypted_master_secret[len(encrypted_master_secret) // 2 :]
|
||||
salt = _get_salt(identifier)
|
||||
salt = _get_salt(identifier, extendable)
|
||||
for i in reversed(range(_ROUND_COUNT)):
|
||||
(l, r) = (
|
||||
r,
|
||||
@ -178,6 +192,7 @@ def split_ems(
|
||||
tuple[int, int]
|
||||
], # A collection of (member_threshold, member_count).
|
||||
identifier: int,
|
||||
extendable: bool,
|
||||
iteration_exponent: int,
|
||||
encrypted_master_secret: bytes, # The encrypted master secret to split.
|
||||
) -> list[list[str]]:
|
||||
@ -218,6 +233,7 @@ def split_ems(
|
||||
group_mnemonics.append(
|
||||
_encode_mnemonic(
|
||||
identifier,
|
||||
extendable,
|
||||
iteration_exponent,
|
||||
group_index,
|
||||
group_threshold,
|
||||
@ -231,11 +247,11 @@ def split_ems(
|
||||
return mnemonics
|
||||
|
||||
|
||||
def recover_ems(mnemonics: list[str]) -> tuple[int, int, bytes]:
|
||||
def recover_ems(mnemonics: list[str]) -> tuple[int, bool, int, bytes]:
|
||||
"""
|
||||
Combines mnemonic shares to obtain the encrypted master secret which was previously
|
||||
split using Shamir's secret sharing scheme.
|
||||
Returns identifier, iteration exponent and the encrypted master secret.
|
||||
Returns identifier, extendable backup flag, iteration exponent and the encrypted master secret.
|
||||
"""
|
||||
|
||||
if not mnemonics:
|
||||
@ -243,6 +259,7 @@ def recover_ems(mnemonics: list[str]) -> tuple[int, int, bytes]:
|
||||
|
||||
(
|
||||
identifier,
|
||||
extendable,
|
||||
iteration_exponent,
|
||||
group_threshold,
|
||||
_group_count,
|
||||
@ -266,7 +283,7 @@ def recover_ems(mnemonics: list[str]) -> tuple[int, int, bytes]:
|
||||
]
|
||||
|
||||
encrypted_master_secret = _recover_secret(group_threshold, group_shares)
|
||||
return identifier, iteration_exponent, encrypted_master_secret
|
||||
return identifier, extendable, iteration_exponent, encrypted_master_secret
|
||||
|
||||
|
||||
def decode_mnemonic(mnemonic: str) -> Share:
|
||||
@ -283,12 +300,16 @@ def decode_mnemonic(mnemonic: str) -> Share:
|
||||
if padding_len > 8:
|
||||
raise MnemonicError("Invalid mnemonic length.")
|
||||
|
||||
if not _rs1024_verify_checksum(mnemonic_data):
|
||||
id_exp_int = _int_from_indices(mnemonic_data[:_ID_EXP_LENGTH_WORDS])
|
||||
identifier = id_exp_int >> (
|
||||
_EXTENDABLE_FLAG_LENGTH_BITS + _ITERATION_EXP_LENGTH_BITS
|
||||
)
|
||||
extendable = bool((id_exp_int >> _ITERATION_EXP_LENGTH_BITS) & 1)
|
||||
iteration_exponent = id_exp_int & ((1 << _ITERATION_EXP_LENGTH_BITS) - 1)
|
||||
|
||||
if not _rs1024_verify_checksum(mnemonic_data, extendable):
|
||||
raise MnemonicError("Invalid mnemonic checksum.")
|
||||
|
||||
id_exp_int = _int_from_indices(mnemonic_data[:_ID_EXP_LENGTH_WORDS])
|
||||
identifier = id_exp_int >> _ITERATION_EXP_LENGTH_BITS
|
||||
iteration_exponent = id_exp_int & ((1 << _ITERATION_EXP_LENGTH_BITS) - 1)
|
||||
tmp = _int_from_indices(
|
||||
mnemonic_data[_ID_EXP_LENGTH_WORDS : _ID_EXP_LENGTH_WORDS + 2]
|
||||
)
|
||||
@ -314,6 +335,7 @@ def decode_mnemonic(mnemonic: str) -> Share:
|
||||
|
||||
return Share(
|
||||
identifier,
|
||||
extendable,
|
||||
iteration_exponent,
|
||||
group_index,
|
||||
group_threshold + 1,
|
||||
@ -352,13 +374,22 @@ def _mnemonic_to_indices(mnemonic: str) -> Iterable[int]:
|
||||
# === Checksum functions ===
|
||||
|
||||
|
||||
def _rs1024_create_checksum(data: Indices) -> Indices:
|
||||
def _customization_string(extendable: bool) -> bytes:
|
||||
if extendable:
|
||||
return _CUSTOMIZATION_STRING_EXTENDABLE
|
||||
else:
|
||||
return _CUSTOMIZATION_STRING_ORIG
|
||||
|
||||
|
||||
def _rs1024_create_checksum(data: Indices, extendable: bool) -> Indices:
|
||||
"""
|
||||
This implements the checksum - a Reed-Solomon code over GF(1024) that guarantees
|
||||
detection of any error affecting at most 3 words and has less than a 1 in 10^9
|
||||
chance of failing to detect more errors.
|
||||
"""
|
||||
values = tuple(_CUSTOMIZATION_STRING) + data + _CHECKSUM_LENGTH_WORDS * (0,)
|
||||
values = (
|
||||
tuple(_customization_string(extendable)) + data + _CHECKSUM_LENGTH_WORDS * (0,)
|
||||
)
|
||||
polymod = _rs1024_polymod(values) ^ 1
|
||||
return tuple(
|
||||
(polymod >> 10 * i) & 1023 for i in reversed(range(_CHECKSUM_LENGTH_WORDS))
|
||||
@ -387,11 +418,11 @@ def _rs1024_polymod(values: Indices) -> int:
|
||||
return chk
|
||||
|
||||
|
||||
def _rs1024_verify_checksum(data: Indices) -> bool:
|
||||
def _rs1024_verify_checksum(data: Indices, extendable: bool) -> bool:
|
||||
"""
|
||||
Verifies a checksum of the given mnemonic, which was already parsed into Indices.
|
||||
"""
|
||||
return _rs1024_polymod(tuple(_CUSTOMIZATION_STRING) + data) == 1
|
||||
return _rs1024_polymod(tuple(_customization_string(extendable)) + data) == 1
|
||||
|
||||
|
||||
# === Internal functions ===
|
||||
@ -409,8 +440,11 @@ def _round_function(i: int, passphrase: bytes, e: int, salt: bytes, r: bytes) ->
|
||||
).key()[: len(r)]
|
||||
|
||||
|
||||
def _get_salt(identifier: int) -> bytes:
|
||||
return _CUSTOMIZATION_STRING + identifier.to_bytes(
|
||||
def _get_salt(identifier: int, extendable: bool) -> bytes:
|
||||
if extendable:
|
||||
return bytes()
|
||||
else:
|
||||
return _CUSTOMIZATION_STRING_ORIG + identifier.to_bytes(
|
||||
_bits_to_bytes(_ID_LENGTH_BITS), "big"
|
||||
)
|
||||
|
||||
@ -481,12 +515,17 @@ def _recover_secret(threshold: int, shares: list[tuple[int, bytes]]) -> bytes:
|
||||
|
||||
def _group_prefix(
|
||||
identifier: int,
|
||||
extendable: bool,
|
||||
iteration_exponent: int,
|
||||
group_index: int,
|
||||
group_threshold: int,
|
||||
group_count: int,
|
||||
) -> Indices:
|
||||
id_exp_int = (identifier << _ITERATION_EXP_LENGTH_BITS) + iteration_exponent
|
||||
id_exp_int = (
|
||||
(identifier << (_EXTENDABLE_FLAG_LENGTH_BITS + _ITERATION_EXP_LENGTH_BITS))
|
||||
+ (int(extendable) << _ITERATION_EXP_LENGTH_BITS)
|
||||
+ iteration_exponent
|
||||
)
|
||||
return tuple(_int_to_indices(id_exp_int, _ID_EXP_LENGTH_WORDS, _RADIX_BITS)) + (
|
||||
(group_index << 6) + ((group_threshold - 1) << 2) + ((group_count - 1) >> 2),
|
||||
)
|
||||
@ -494,6 +533,7 @@ def _group_prefix(
|
||||
|
||||
def _encode_mnemonic(
|
||||
identifier: int,
|
||||
extendable: bool,
|
||||
iteration_exponent: int,
|
||||
group_index: int, # The x coordinate of the group share.
|
||||
group_threshold: int, # The number of group shares needed to reconstruct the encrypted master secret.
|
||||
@ -513,7 +553,12 @@ def _encode_mnemonic(
|
||||
|
||||
share_data = (
|
||||
_group_prefix(
|
||||
identifier, iteration_exponent, group_index, group_threshold, group_count
|
||||
identifier,
|
||||
extendable,
|
||||
iteration_exponent,
|
||||
group_index,
|
||||
group_threshold,
|
||||
group_count,
|
||||
)
|
||||
+ (
|
||||
(((group_count - 1) & 3) << 8)
|
||||
@ -522,15 +567,16 @@ def _encode_mnemonic(
|
||||
)
|
||||
+ tuple(_int_to_indices(value_int, value_word_count, _RADIX_BITS))
|
||||
)
|
||||
checksum = _rs1024_create_checksum(share_data)
|
||||
checksum = _rs1024_create_checksum(share_data, extendable)
|
||||
|
||||
return _mnemonic_from_indices(share_data + checksum)
|
||||
|
||||
|
||||
def _decode_mnemonics(
|
||||
mnemonics: list[str],
|
||||
) -> tuple[int, int, int, int, MnemonicGroups]:
|
||||
) -> tuple[int, bool, int, int, int, MnemonicGroups]:
|
||||
identifiers = set()
|
||||
extendable_flags = set()
|
||||
iteration_exponents = set()
|
||||
group_thresholds = set()
|
||||
group_counts = set()
|
||||
@ -540,6 +586,7 @@ def _decode_mnemonics(
|
||||
for mnemonic in mnemonics:
|
||||
share = decode_mnemonic(mnemonic)
|
||||
identifiers.add(share.identifier)
|
||||
extendable_flags.add(share.extendable)
|
||||
iteration_exponents.add(share.iteration_exponent)
|
||||
group_thresholds.add(share.group_threshold)
|
||||
group_counts.add(share.group_count)
|
||||
@ -550,7 +597,11 @@ def _decode_mnemonics(
|
||||
)
|
||||
group[1].add((share.index, share.share_value))
|
||||
|
||||
if len(identifiers) != 1 or len(iteration_exponents) != 1:
|
||||
if (
|
||||
len(identifiers) != 1
|
||||
or len(extendable_flags) != 1
|
||||
or len(iteration_exponents) != 1
|
||||
):
|
||||
raise MnemonicError(
|
||||
f"Invalid set of mnemonics. All mnemonics must begin with the same {_ID_EXP_LENGTH_WORDS} words."
|
||||
)
|
||||
@ -573,6 +624,7 @@ def _decode_mnemonics(
|
||||
|
||||
return (
|
||||
identifiers.pop(),
|
||||
extendable_flags.pop(),
|
||||
iteration_exponents.pop(),
|
||||
group_thresholds.pop(),
|
||||
group_counts.pop(),
|
||||
|
@ -279,4 +279,38 @@ vectors = [
|
||||
],
|
||||
"",
|
||||
],
|
||||
[
|
||||
[
|
||||
"herald flea academic cage avoid space trend estate dryer hairy evoke eyebrow improve airline artwork garlic premium duration prevent oven",
|
||||
"herald flea academic client blue skunk class goat luxury deny presence impulse graduate clay join blanket bulge survive dish necklace",
|
||||
"herald flea academic acne advance fused brother frozen broken game ranked ajar already believe check install theory angry exercise adult"
|
||||
],
|
||||
"ad6f2ad8b59bbbaa01369b9006208d9a",
|
||||
],
|
||||
[
|
||||
[
|
||||
"testify swimming academic academic column loyalty smear include exotic bedroom exotic wrist lobe cover grief golden smart junior estimate learn"
|
||||
],
|
||||
"1679b4516e0ee5954351d288a838f45e",
|
||||
],
|
||||
[
|
||||
[
|
||||
"enemy favorite academic acid cowboy phrase havoc level response walnut budget painting inside trash adjust froth kitchen learn tidy punish",
|
||||
"enemy favorite academic always academic sniff script carpet romp kind promise scatter center unfair training emphasis evening belong fake enforce"
|
||||
],
|
||||
"48b1a4b80b8c209ad42c33672bdaa428",
|
||||
],
|
||||
[
|
||||
[
|
||||
"impulse calcium academic academic alcohol sugar lyrics pajamas column facility finance tension extend space birthday rainbow swimming purple syndrome facility trial warn duration snapshot shadow hormone rhyme public spine counter easy hawk album"
|
||||
],
|
||||
"8340611602fe91af634a5f4608377b5235fa2d757c51d720c0c7656249a3035f",
|
||||
],
|
||||
[
|
||||
[
|
||||
"western apart academic always artist resident briefing sugar woman oven coding club ajar merit pecan answer prisoner artist fraction amount desktop mild false necklace muscle photo wealthy alpha category unwrap spew losing making",
|
||||
"western apart academic acid answer ancient auction flip image penalty oasis beaver multiple thunder problem switch alive heat inherit superior teaspoon explain blanket pencil numb lend punish endless aunt garlic humidity kidney observe"
|
||||
],
|
||||
"8dc652d6d6cd370d8c963141f6d79ba440300f25c467302c1d966bff8f62300d",
|
||||
],
|
||||
]
|
||||
|
@ -190,8 +190,8 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
"talent drug much home firefly toxic analysis idea umbrella slice",
|
||||
]
|
||||
passphrase = b"TREZOR"
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
master_secret = slip39.decrypt(ems, passphrase, exponent, identifier)
|
||||
identifier, extendable, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
master_secret = slip39.decrypt(ems, passphrase, exponent, identifier, extendable)
|
||||
|
||||
node = cardano.from_seed_slip23(master_secret)
|
||||
|
||||
@ -264,8 +264,8 @@ class TestCardanoAddress(unittest.TestCase):
|
||||
"quick silent downtown oral critical step remove says rhythm venture aunt",
|
||||
]
|
||||
passphrase = b"TREZOR"
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
master_secret = slip39.decrypt(ems, passphrase, exponent, identifier)
|
||||
identifier, extendable, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
master_secret = slip39.decrypt(ems, passphrase, exponent, identifier, extendable)
|
||||
|
||||
node = cardano.from_seed_slip23(master_secret)
|
||||
|
||||
|
@ -144,8 +144,8 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
"talent drug much home firefly toxic analysis idea umbrella slice",
|
||||
]
|
||||
passphrase = b"TREZOR"
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
master_secret = slip39.decrypt(ems, passphrase, exponent, identifier)
|
||||
identifier, extendable, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
master_secret = slip39.decrypt(ems, passphrase, exponent, identifier, extendable)
|
||||
|
||||
node = cardano.from_seed_slip23(master_secret)
|
||||
|
||||
@ -193,8 +193,8 @@ class TestCardanoGetPublicKey(unittest.TestCase):
|
||||
"quick silent downtown oral critical step remove says rhythm venture aunt",
|
||||
]
|
||||
passphrase = b"TREZOR"
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
master_secret = slip39.decrypt(ems, passphrase, exponent, identifier)
|
||||
identifier, extendable, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
master_secret = slip39.decrypt(ems, passphrase, exponent, identifier, extendable)
|
||||
|
||||
node = cardano.from_seed_slip23(master_secret)
|
||||
|
||||
|
@ -41,6 +41,7 @@ class TestSlip39(unittest.TestCase):
|
||||
share.iteration_exponent, storage.recovery.get_slip39_iteration_exponent()
|
||||
)
|
||||
self.assertEqual(share.identifier, storage.recovery.get_slip39_identifier())
|
||||
self.assertEqual(share.extendable, storage.recovery.get_slip39_extendable())
|
||||
self.assertEqual(storage.recovery.get_slip39_remaining_shares(0), 2)
|
||||
self.assertEqual(
|
||||
storage.recovery_shares.get(share.index, share.group_index), first
|
||||
@ -84,6 +85,7 @@ class TestSlip39(unittest.TestCase):
|
||||
share.iteration_exponent, storage.recovery.get_slip39_iteration_exponent()
|
||||
)
|
||||
self.assertEqual(share.identifier, storage.recovery.get_slip39_identifier())
|
||||
self.assertEqual(share.extendable, storage.recovery.get_slip39_extendable())
|
||||
self.assertEqual(
|
||||
storage.recovery.fetch_slip39_remaining_shares(), [16, 0, 16, 16]
|
||||
)
|
||||
@ -100,6 +102,7 @@ class TestSlip39(unittest.TestCase):
|
||||
share.iteration_exponent, storage.recovery.get_slip39_iteration_exponent()
|
||||
)
|
||||
self.assertEqual(share.identifier, storage.recovery.get_slip39_identifier())
|
||||
self.assertEqual(share.extendable, storage.recovery.get_slip39_extendable())
|
||||
self.assertEqual(
|
||||
storage.recovery_shares.get(share.index, share.group_index), words
|
||||
)
|
||||
@ -122,6 +125,7 @@ class TestSlip39(unittest.TestCase):
|
||||
share.iteration_exponent, storage.recovery.get_slip39_iteration_exponent()
|
||||
)
|
||||
self.assertEqual(share.identifier, storage.recovery.get_slip39_identifier())
|
||||
self.assertEqual(share.extendable, storage.recovery.get_slip39_extendable())
|
||||
self.assertEqual(
|
||||
storage.recovery_shares.get(share.index, share.group_index), words
|
||||
)
|
||||
@ -146,6 +150,7 @@ class TestSlip39(unittest.TestCase):
|
||||
share.iteration_exponent, storage.recovery.get_slip39_iteration_exponent()
|
||||
)
|
||||
self.assertEqual(share.identifier, storage.recovery.get_slip39_identifier())
|
||||
self.assertEqual(share.extendable, storage.recovery.get_slip39_extendable())
|
||||
self.assertEqual(
|
||||
storage.recovery_shares.get(share.index, share.group_index), words
|
||||
)
|
||||
|
@ -30,45 +30,50 @@ class TestCryptoSlip39(unittest.TestCase):
|
||||
def test_basic_sharing_random(self):
|
||||
ems = random.bytes(32)
|
||||
identifier = slip39.generate_random_identifier()
|
||||
mnemonics = slip39.split_ems(1, [(3, 5)], identifier, 1, ems)
|
||||
for extendable in (False, True):
|
||||
mnemonics = slip39.split_ems(1, [(3, 5)], identifier, extendable, 1, ems)
|
||||
mnemonics = mnemonics[0]
|
||||
self.assertEqual(
|
||||
slip39.recover_ems(mnemonics[:3]), slip39.recover_ems(mnemonics[2:])
|
||||
)
|
||||
|
||||
def test_basic_sharing_fixed(self):
|
||||
for extendable in (False, True):
|
||||
generated_identifier = slip39.generate_random_identifier()
|
||||
mnemonics = slip39.split_ems(1, [(3, 5)], generated_identifier, 1, self.EMS)
|
||||
mnemonics = slip39.split_ems(1, [(3, 5)], generated_identifier, extendable, 1, self.EMS)
|
||||
mnemonics = mnemonics[0]
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonics[:3])
|
||||
identifier, _, _, ems = slip39.recover_ems(mnemonics[:3])
|
||||
self.assertEqual(ems, self.EMS)
|
||||
self.assertEqual(generated_identifier, identifier)
|
||||
self.assertEqual(slip39.recover_ems(mnemonics[1:4])[2], ems)
|
||||
self.assertEqual(slip39.recover_ems(mnemonics[1:4])[3], ems)
|
||||
with self.assertRaises(slip39.MnemonicError):
|
||||
slip39.recover_ems(mnemonics[1:3])
|
||||
|
||||
def test_iteration_exponent(self):
|
||||
for extendable in (False, True):
|
||||
identifier = slip39.generate_random_identifier()
|
||||
mnemonics = slip39.split_ems(1, [(3, 5)], identifier, 1, self.EMS)
|
||||
mnemonics = slip39.split_ems(1, [(3, 5)], identifier, extendable, 1, self.EMS)
|
||||
mnemonics = mnemonics[0]
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonics[1:4])
|
||||
identifier, extendable, exponent, ems = slip39.recover_ems(mnemonics[1:4])
|
||||
self.assertEqual(ems, self.EMS)
|
||||
|
||||
identifier = slip39.generate_random_identifier()
|
||||
mnemonics = slip39.split_ems(1, [(3, 5)], identifier, 2, self.EMS)
|
||||
mnemonics = slip39.split_ems(1, [(3, 5)], identifier, extendable, 2, self.EMS)
|
||||
mnemonics = mnemonics[0]
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonics[1:4])
|
||||
identifier, extendable, exponent, ems = slip39.recover_ems(mnemonics[1:4])
|
||||
self.assertEqual(ems, self.EMS)
|
||||
|
||||
def test_group_sharing(self):
|
||||
group_threshold = 2
|
||||
group_sizes = (5, 3, 5, 1)
|
||||
member_thresholds = (3, 2, 2, 1)
|
||||
for extendable in (False, True):
|
||||
identifier = slip39.generate_random_identifier()
|
||||
mnemonics = slip39.split_ems(
|
||||
group_threshold,
|
||||
list(zip(member_thresholds, group_sizes)),
|
||||
identifier,
|
||||
extendable,
|
||||
1,
|
||||
self.EMS,
|
||||
)
|
||||
@ -79,16 +84,16 @@ class TestCryptoSlip39(unittest.TestCase):
|
||||
for group2_subset in combinations(groups[1][0], groups[1][1]):
|
||||
mnemonic_subset = list(group1_subset + group2_subset)
|
||||
random.shuffle(mnemonic_subset)
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonic_subset)
|
||||
identifier, _, _, ems = slip39.recover_ems(mnemonic_subset)
|
||||
self.assertEqual(ems, self.EMS)
|
||||
|
||||
# Minimal sets of mnemonics.
|
||||
identifier, exponent, ems = slip39.recover_ems(
|
||||
identifier, _, _, ems = slip39.recover_ems(
|
||||
[mnemonics[2][0], mnemonics[2][2], mnemonics[3][0]]
|
||||
)
|
||||
self.assertEqual(ems, self.EMS)
|
||||
self.assertEqual(
|
||||
slip39.recover_ems([mnemonics[2][3], mnemonics[3][0], mnemonics[2][4]])[2],
|
||||
slip39.recover_ems([mnemonics[2][3], mnemonics[3][0], mnemonics[2][4]])[3],
|
||||
ems,
|
||||
)
|
||||
|
||||
@ -104,11 +109,13 @@ class TestCryptoSlip39(unittest.TestCase):
|
||||
group_threshold = 1
|
||||
group_sizes = (5, 3, 5, 1)
|
||||
member_thresholds = (3, 2, 2, 1)
|
||||
for extendable in (False, True):
|
||||
identifier = slip39.generate_random_identifier()
|
||||
mnemonics = slip39.split_ems(
|
||||
group_threshold,
|
||||
list(zip(member_thresholds, group_sizes)),
|
||||
identifier,
|
||||
extendable,
|
||||
1,
|
||||
self.EMS,
|
||||
)
|
||||
@ -118,16 +125,18 @@ class TestCryptoSlip39(unittest.TestCase):
|
||||
for group_subset in combinations(group, threshold):
|
||||
mnemonic_subset = list(group_subset)
|
||||
random.shuffle(mnemonic_subset)
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonic_subset)
|
||||
identifier, _, _, ems = slip39.recover_ems(mnemonic_subset)
|
||||
self.assertEqual(ems, self.EMS)
|
||||
|
||||
def test_all_groups_exist(self):
|
||||
for extendable in (False, True):
|
||||
for group_threshold in (1, 2, 5):
|
||||
identifier = slip39.generate_random_identifier()
|
||||
mnemonics = slip39.split_ems(
|
||||
group_threshold,
|
||||
[(3, 5), (1, 1), (2, 3), (2, 5), (3, 5)],
|
||||
identifier,
|
||||
extendable,
|
||||
1,
|
||||
self.EMS,
|
||||
)
|
||||
@ -135,34 +144,35 @@ class TestCryptoSlip39(unittest.TestCase):
|
||||
self.assertEqual(len(sum(mnemonics, [])), 19)
|
||||
|
||||
def test_invalid_sharing(self):
|
||||
for extendable in (False, True):
|
||||
identifier = slip39.generate_random_identifier()
|
||||
|
||||
# Group threshold exceeds number of groups.
|
||||
with self.assertRaises(ValueError):
|
||||
slip39.split_ems(3, [(3, 5), (2, 5)], identifier, 1, self.EMS)
|
||||
slip39.split_ems(3, [(3, 5), (2, 5)], identifier, extendable, 1, self.EMS)
|
||||
|
||||
# Invalid group threshold.
|
||||
with self.assertRaises(ValueError):
|
||||
slip39.split_ems(0, [(3, 5), (2, 5)], identifier, 1, self.EMS)
|
||||
slip39.split_ems(0, [(3, 5), (2, 5)], identifier, extendable, 1, self.EMS)
|
||||
|
||||
# Member threshold exceeds number of members.
|
||||
with self.assertRaises(ValueError):
|
||||
slip39.split_ems(2, [(3, 2), (2, 5)], identifier, 1, self.EMS)
|
||||
slip39.split_ems(2, [(3, 2), (2, 5)], identifier, extendable, 1, self.EMS)
|
||||
|
||||
# Invalid member threshold.
|
||||
with self.assertRaises(ValueError):
|
||||
slip39.split_ems(2, [(0, 2), (2, 5)], identifier, 1, self.EMS)
|
||||
slip39.split_ems(2, [(0, 2), (2, 5)], identifier, extendable, 1, self.EMS)
|
||||
|
||||
# Group with multiple members and threshold 1.
|
||||
with self.assertRaises(ValueError):
|
||||
slip39.split_ems(2, [(3, 5), (1, 3), (2, 5)], identifier, 1, self.EMS)
|
||||
slip39.split_ems(2, [(3, 5), (1, 3), (2, 5)], identifier, extendable, 1, self.EMS)
|
||||
|
||||
def test_vectors(self):
|
||||
for mnemonics, secret in vectors:
|
||||
if secret:
|
||||
identifier, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
identifier, extendable, exponent, ems = slip39.recover_ems(mnemonics)
|
||||
self.assertEqual(
|
||||
slip39.decrypt(ems, b"TREZOR", exponent, identifier),
|
||||
slip39.decrypt(ems, b"TREZOR", exponent, identifier, extendable),
|
||||
unhexlify(secret),
|
||||
)
|
||||
else:
|
||||
|
@ -31,10 +31,10 @@ applying PBKDF2 to the mnemonic secret plus passphrase.
|
||||
|
||||
For SLIP-39 it is not practical to store the raw data of the recovery shares. During
|
||||
device initialization, a random Encrypted Master Secret is generated and stored as
|
||||
`_MNEMONIC_SECRET`. SLIP-39 encryption parameters (a random identifier and an iteration
|
||||
exponent) are stored alongside the mnemonic secret in their own storage fields. Whenever
|
||||
the root node is required, it is derived by "decrypting" the stored mnemonic secret with
|
||||
the provided passphrase.
|
||||
`_MNEMONIC_SECRET`. SLIP-39 encryption parameters (a random identifier, extendable backup
|
||||
flag and an iteration exponent) are stored alongside the mnemonic secret in their own
|
||||
storage fields. Whenever the root node is required, it is derived by "decrypting" the
|
||||
stored mnemonic secret with the provided passphrase.
|
||||
|
||||
|
||||
## SLIP-39 implementation
|
||||
@ -46,17 +46,17 @@ SLIP-39 provides the following high-level API:
|
||||
Secret with the provided passphrase, and split into a number of shares defined via
|
||||
the group parameters.
|
||||
Implemented using the following:
|
||||
- `encrypt(master_secret, passphrase, iteration_exponent, identifier)`: Encrypt the
|
||||
Master Secret with the given passphrase and parameters.
|
||||
- **`split_ems(group parameters, identifier, iteration_exponent, encrypted_master_secret)`**:
|
||||
- `encrypt(master_secret, passphrase, iteration_exponent, identifier, extendable)`:
|
||||
Encrypt the Master Secret with the given passphrase and parameters.
|
||||
- **`split_ems(group parameters, identifier, extendable, iteration_exponent, encrypted_master_secret)`**:
|
||||
Split the encrypted secret and encode the metadata into a set of shares defined via
|
||||
the group parameters.
|
||||
* `combine_mnemonics(set of shares, passphrase)`: Combine the given set of shares to
|
||||
reconstruct the secret, then decrypt it with the provided passphrase.
|
||||
Implemented using the following:
|
||||
- **`recover_ems(set of shares)`**: Combine the given set of shares to obtain the
|
||||
encrypted master secret, identifier and iteration exponent.
|
||||
- **`decrypt(encrypted_master_secret, passphrase, iteration_exponent, identifier)`**:
|
||||
encrypted master secret, identifier, extendable backup flag and iteration exponent.
|
||||
- **`decrypt(encrypted_master_secret, passphrase, iteration_exponent, identifier, extendable)`**:
|
||||
Decrypt the secret with the given passphrase and parameters, to obtain the original
|
||||
Master Secret.
|
||||
|
||||
@ -74,7 +74,7 @@ This process does not use passphrase.
|
||||
|
||||
1. Generate the required number of random bits (128 or 256), and store as
|
||||
`_MNEMONIC_SECRET`.
|
||||
2. Generate a random identifier and store as `_SLIP39_IDENTIFIER`.
|
||||
2. Translate the host-specified backup type to an extendable backup type and store it as `_BACKUP_TYPE`.
|
||||
3. Store the default iteration exponent `1` as `_SLIP39_ITERATION_EXPONENT`.
|
||||
4. The storage now contains all parameters required for seed derivation.
|
||||
|
||||
@ -100,6 +100,7 @@ This process does not use passphrase.
|
||||
1. Prompt the user to enter enough shares.
|
||||
2. Use `slip39.recover_ems(shares)` to combine the shares and get metadata.
|
||||
3. Store the Encrypted Master Secret as `_MNEMONIC_SECRET`.
|
||||
4. Store the identifier as `_SLIP39_IDENTIFIER`.
|
||||
5. Store the iteration exponent as `_SLIP39_ITERATION_EXPONENT`.
|
||||
6. The storage now contains all parameters required for seed derivation.
|
||||
4. Infer the backup type and store it as `_BACKUP_TYPE`.
|
||||
5. If the backup type is not extendable, then store the identifier as `_SLIP39_IDENTIFIER`.
|
||||
6. Store the iteration exponent as `_SLIP39_ITERATION_EXPONENT`.
|
||||
7. The storage now contains all parameters required for seed derivation.
|
||||
|
111
poetry.lock
generated
111
poetry.lock
generated
@ -1,9 +1,10 @@
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "astroid"
|
||||
version = "2.15.8"
|
||||
description = "An abstract syntax tree for Python with inference support."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
files = [
|
||||
@ -23,6 +24,7 @@ wrapt = [
|
||||
name = "atomicwrites"
|
||||
version = "1.4.0"
|
||||
description = "Atomic file writes."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
@ -34,6 +36,7 @@ files = [
|
||||
name = "attrs"
|
||||
version = "21.4.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
@ -51,6 +54,7 @@ tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy"
|
||||
name = "autoflake"
|
||||
version = "1.4"
|
||||
description = "Removes unused imports and unused variables"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -64,6 +68,7 @@ pyflakes = ">=1.1.0"
|
||||
name = "binsize"
|
||||
version = "0.1.3"
|
||||
description = "Tool to analyze the size of a binary from .elf file"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7,<4.0"
|
||||
files = [
|
||||
@ -81,6 +86,7 @@ typing-extensions = "*"
|
||||
name = "black"
|
||||
version = "24.3.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
@ -127,6 +133,7 @@ uvloop = ["uvloop (>=0.15.2)"]
|
||||
name = "certifi"
|
||||
version = "2023.7.22"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -138,6 +145,7 @@ files = [
|
||||
name = "cffi"
|
||||
version = "1.15.0"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -200,6 +208,7 @@ pycparser = "*"
|
||||
name = "charset-normalizer"
|
||||
version = "2.0.11"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5.0"
|
||||
files = [
|
||||
@ -214,6 +223,7 @@ unicode-backport = ["unicodedata2"]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -228,6 +238,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
name = "click-default-group"
|
||||
version = "1.2.2"
|
||||
description = "Extends click.Group to invoke a command without explicit subcommand name"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -241,6 +252,7 @@ click = "*"
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
@ -252,6 +264,7 @@ files = [
|
||||
name = "construct"
|
||||
version = "2.10.67"
|
||||
description = "A powerful declarative symmetric parser/builder for binary data"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -265,6 +278,7 @@ extras = ["arrow", "cloudpickle", "enum34", "lz4", "numpy", "ruamel.yaml"]
|
||||
name = "construct-classes"
|
||||
version = "0.1.2"
|
||||
description = "Parse your binary structs into dataclasses"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2,<4.0"
|
||||
files = [
|
||||
@ -279,6 +293,7 @@ construct = ">=2.10,<3.0"
|
||||
name = "coverage"
|
||||
version = "4.5.4"
|
||||
description = "Code coverage measurement for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
|
||||
files = [
|
||||
@ -320,6 +335,7 @@ files = [
|
||||
name = "cryptography"
|
||||
version = "42.0.4"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -374,6 +390,7 @@ test-randomorder = ["pytest-randomly"]
|
||||
name = "demjson3"
|
||||
version = "3.0.5"
|
||||
description = "encoder, decoder, and lint/validator for JSON (JavaScript Object Notation) compliant with RFC 7159"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -384,6 +401,7 @@ files = [
|
||||
name = "dill"
|
||||
version = "0.3.8"
|
||||
description = "serialize all of Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
@ -399,6 +417,7 @@ profile = ["gprof2dot (>=2022.7.29)"]
|
||||
name = "distlib"
|
||||
version = "0.3.4"
|
||||
description = "Distribution utilities"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -410,6 +429,7 @@ files = [
|
||||
name = "dominate"
|
||||
version = "2.6.0"
|
||||
description = "Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
@ -421,6 +441,7 @@ files = [
|
||||
name = "ecdsa"
|
||||
version = "0.16.1"
|
||||
description = "ECDSA cryptographic signature library (pure python)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
@ -439,6 +460,7 @@ gmpy2 = ["gmpy2"]
|
||||
name = "execnet"
|
||||
version = "1.9.0"
|
||||
description = "execnet: rapid multi-Python deployment"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
@ -453,6 +475,7 @@ testing = ["pre-commit"]
|
||||
name = "fido2"
|
||||
version = "0.8.1"
|
||||
description = "Python based FIDO 2.0 library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7.6,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
files = [
|
||||
@ -471,6 +494,7 @@ pcsc = ["pyscard"]
|
||||
name = "filelock"
|
||||
version = "3.4.2"
|
||||
description = "A platform independent file lock."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -486,6 +510,7 @@ testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-co
|
||||
name = "flake8"
|
||||
version = "7.0.0"
|
||||
description = "the modular source code checker: pep8 pyflakes and co"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8.1"
|
||||
files = [
|
||||
@ -502,6 +527,7 @@ pyflakes = ">=3.2.0,<3.3.0"
|
||||
name = "flake8-requirements"
|
||||
version = "2.1.0"
|
||||
description = "Package requirements checker, plugin for flake8"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -520,6 +546,7 @@ pyproject = ["Flake8-pyproject"]
|
||||
name = "flaky"
|
||||
version = "3.7.0"
|
||||
description = "Plugin for nose or pytest that automatically reruns flaky tests."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
@ -531,6 +558,7 @@ files = [
|
||||
name = "graphviz"
|
||||
version = "0.19.1"
|
||||
description = "Simple Python interface for Graphviz"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -547,6 +575,7 @@ test = ["coverage", "mock (>=4)", "pytest (>=6)", "pytest-cov", "pytest-mock (>=
|
||||
name = "hypothesis"
|
||||
version = "6.36.1"
|
||||
description = "A library for property-based testing"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -578,6 +607,7 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2021.5)"]
|
||||
name = "idna"
|
||||
version = "3.7"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
@ -589,6 +619,7 @@ files = [
|
||||
name = "importlib-resources"
|
||||
version = "5.12.0"
|
||||
description = "Read resources from Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -607,6 +638,7 @@ testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-chec
|
||||
name = "incremental"
|
||||
version = "21.3.0"
|
||||
description = "A small library that versions your Python projects."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -621,6 +653,7 @@ scripts = ["click (>=6.0)", "twisted (>=16.4.0)"]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
description = "iniconfig: brain-dead simple config-ini parsing"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -632,6 +665,7 @@ files = [
|
||||
name = "inotify"
|
||||
version = "0.2.10"
|
||||
description = "An adapter to Linux kernel support for inotify directory-watching."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -646,6 +680,7 @@ nose = "*"
|
||||
name = "isort"
|
||||
version = "5.11.5"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
@ -663,6 +698,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
description = "A very fast and expressive template engine."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -680,6 +716,7 @@ i18n = ["Babel (>=2.7)"]
|
||||
name = "lazy-object-proxy"
|
||||
version = "1.7.1"
|
||||
description = "A fast and thorough lazy object proxy."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -726,6 +763,7 @@ files = [
|
||||
name = "libusb1"
|
||||
version = "3.0.0"
|
||||
description = "Pure-python wrapper for libusb-1.0"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -739,6 +777,7 @@ files = [
|
||||
name = "mako"
|
||||
version = "1.1.6"
|
||||
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
@ -757,6 +796,7 @@ lingua = ["lingua"]
|
||||
name = "markupsafe"
|
||||
version = "2.0.1"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -835,6 +875,7 @@ files = [
|
||||
name = "mccabe"
|
||||
version = "0.7.0"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -846,6 +887,7 @@ files = [
|
||||
name = "mnemonic"
|
||||
version = "0.20"
|
||||
description = "Implementation of Bitcoin BIP-0039"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
@ -857,6 +899,7 @@ files = [
|
||||
name = "munch"
|
||||
version = "2.5.0"
|
||||
description = "A dot-accessible dictionary (a la JavaScript objects)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -875,6 +918,7 @@ yaml = ["PyYAML (>=5.1.0)"]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -886,6 +930,7 @@ files = [
|
||||
name = "nanopb"
|
||||
version = "0.4.5.post1"
|
||||
description = "Nanopb is a small code-size Protocol Buffers implementation in ansi C. It is especially suitable for use in microcontrollers, but fits any memory restricted system."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7"
|
||||
files = [
|
||||
@ -903,6 +948,7 @@ grpcio-tools = ["grpcio-tools (>=1.26.0rc1)"]
|
||||
name = "nose"
|
||||
version = "1.3.7"
|
||||
description = "nose extends unittest to make testing easier"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -915,6 +961,7 @@ files = [
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -926,6 +973,7 @@ files = [
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
files = [
|
||||
@ -937,6 +985,7 @@ files = [
|
||||
name = "pillow"
|
||||
version = "10.3.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
@ -1023,6 +1072,7 @@ xmp = ["defusedxml"]
|
||||
name = "platformdirs"
|
||||
version = "2.6.1"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -1038,6 +1088,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2)", "pytest-
|
||||
name = "pluggy"
|
||||
version = "1.0.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1053,6 +1104,7 @@ testing = ["pytest", "pytest-benchmark"]
|
||||
name = "protobuf"
|
||||
version = "3.19.4"
|
||||
description = "Protocol Buffers"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
@ -1088,6 +1140,7 @@ files = [
|
||||
name = "py"
|
||||
version = "1.11.0"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
files = [
|
||||
@ -1099,6 +1152,7 @@ files = [
|
||||
name = "pyasn1"
|
||||
version = "0.4.8"
|
||||
description = "ASN.1 types and codecs"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -1110,6 +1164,7 @@ files = [
|
||||
name = "pycodestyle"
|
||||
version = "2.11.1"
|
||||
description = "Python style guide checker"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
@ -1121,6 +1176,7 @@ files = [
|
||||
name = "pycparser"
|
||||
version = "2.21"
|
||||
description = "C parser in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
files = [
|
||||
@ -1132,6 +1188,7 @@ files = [
|
||||
name = "pyflakes"
|
||||
version = "3.2.0"
|
||||
description = "passive checker of Python programs"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
@ -1143,6 +1200,7 @@ files = [
|
||||
name = "pylint"
|
||||
version = "2.17.7"
|
||||
description = "python code static checker"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7.2"
|
||||
files = [
|
||||
@ -1172,6 +1230,7 @@ testutils = ["gitpython (>3)"]
|
||||
name = "pyserial"
|
||||
version = "3.5"
|
||||
description = "Python Serial Port Extension"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -1186,6 +1245,7 @@ cp2110 = ["hidapi"]
|
||||
name = "pytest"
|
||||
version = "6.2.5"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1210,6 +1270,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm
|
||||
name = "pytest-forked"
|
||||
version = "1.4.0"
|
||||
description = "run tests in isolated forked subprocesses"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1225,6 +1286,7 @@ pytest = ">=3.10"
|
||||
name = "pytest-ordering"
|
||||
version = "0.6"
|
||||
description = "pytest plugin to run your tests in a specific order"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -1240,6 +1302,7 @@ pytest = "*"
|
||||
name = "pytest-random-order"
|
||||
version = "1.0.4"
|
||||
description = "Randomise the order in which pytest tests are run with some control over the randomness"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5.0"
|
||||
files = [
|
||||
@ -1254,6 +1317,7 @@ pytest = ">=3.0.0"
|
||||
name = "pytest-timeout"
|
||||
version = "2.1.0"
|
||||
description = "pytest plugin to abort hanging tests"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1268,6 +1332,7 @@ pytest = ">=5.0.0"
|
||||
name = "pytest-xdist"
|
||||
version = "2.5.0"
|
||||
description = "pytest xdist plugin for distributed testing and loop-on-failing modes"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1289,6 +1354,7 @@ testing = ["filelock"]
|
||||
name = "python-bitcoinlib"
|
||||
version = "0.11.0"
|
||||
description = "The Swiss Army Knife of the Bitcoin protocol."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -1300,6 +1366,7 @@ files = [
|
||||
name = "pyyaml"
|
||||
version = "6.0.1"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1360,6 +1427,7 @@ files = [
|
||||
name = "requests"
|
||||
version = "2.32.0"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
@ -1381,6 +1449,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
name = "scan-build"
|
||||
version = "2.0.20"
|
||||
description = "static code analyzer wrapper for Clang."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1392,6 +1461,7 @@ files = [
|
||||
name = "scons"
|
||||
version = "4.3.0"
|
||||
description = "Open Source next-generation build tool."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
@ -1406,6 +1476,7 @@ setuptools = "*"
|
||||
name = "setuptools"
|
||||
version = "67.6.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -1420,28 +1491,24 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
|
||||
|
||||
[[package]]
|
||||
name = "shamir-mnemonic"
|
||||
version = "0.2.2"
|
||||
version = "0.3.0"
|
||||
description = "SLIP-39 Shamir Mnemonics"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = "<4.0,>=3.6"
|
||||
files = [
|
||||
{file = "shamir-mnemonic-0.2.2.tar.gz", hash = "sha256:7fb9b592e5c518192c0b0caa2c2d82e342fddd186693bc64be9647eace1b9182"},
|
||||
{file = "shamir_mnemonic-0.2.2-py3-none-any.whl", hash = "sha256:7d9facea70379cad02bab18d4572c0fcd033c9d7effe5da095b9e0944bf5fbbf"},
|
||||
{file = "shamir_mnemonic-0.3.0-py3-none-any.whl", hash = "sha256:188c6b5bd00d5e756e12e2b186c3cb7c98ff7ff44df608d4c1d2077f6b6e730f"},
|
||||
{file = "shamir_mnemonic-0.3.0.tar.gz", hash = "sha256:bc04886a1ddfe2a64d8a3ec51abf0f664d98d5b557cc7e78a8ad2d10a1d87438"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
attrs = "*"
|
||||
click = ">=7,<9"
|
||||
colorama = "*"
|
||||
|
||||
[package.extras]
|
||||
dev = ["black", "flake8", "isort"]
|
||||
tests = ["pytest"]
|
||||
cli = ["click (>=7,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
@ -1453,6 +1520,7 @@ files = [
|
||||
name = "sortedcontainers"
|
||||
version = "2.4.0"
|
||||
description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -1464,6 +1532,7 @@ files = [
|
||||
name = "termcolor"
|
||||
version = "2.3.0"
|
||||
description = "ANSI color formatting for output in terminal"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -1478,6 +1547,7 @@ tests = ["pytest", "pytest-cov"]
|
||||
name = "toiftool"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = []
|
||||
@ -1496,6 +1566,7 @@ url = "python/tools/toiftool"
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
@ -1507,6 +1578,7 @@ files = [
|
||||
name = "tomli"
|
||||
version = "1.2.3"
|
||||
description = "A lil' TOML parser"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1518,6 +1590,7 @@ files = [
|
||||
name = "tomlkit"
|
||||
version = "0.12.4"
|
||||
description = "Style preserving TOML library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -1529,6 +1602,7 @@ files = [
|
||||
name = "towncrier"
|
||||
version = "23.6.0"
|
||||
description = "Building newsfiles for your project."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -1551,6 +1625,7 @@ dev = ["furo", "packaging", "sphinx (>=5)", "twisted"]
|
||||
name = "tox"
|
||||
version = "3.24.5"
|
||||
description = "tox is a generic virtualenv management and test command line tool"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
files = [
|
||||
@ -1576,6 +1651,7 @@ testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psu
|
||||
name = "trezor"
|
||||
version = "0.13.9"
|
||||
description = "Python library for communicating with Trezor Hardware Wallet"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = []
|
||||
@ -1607,6 +1683,7 @@ url = "python"
|
||||
name = "trezor-pylint-plugin"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "^3.8"
|
||||
files = []
|
||||
@ -1624,6 +1701,7 @@ url = "tools/trezor-pylint-plugin"
|
||||
name = "typing-extensions"
|
||||
version = "4.0.1"
|
||||
description = "Backported and Experimental Type Hints for Python 3.6+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1635,6 +1713,7 @@ files = [
|
||||
name = "uhid-freebsd"
|
||||
version = "1.2.2"
|
||||
description = "Get information on FreeBSD uhid devices."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
@ -1645,6 +1724,7 @@ files = [
|
||||
name = "urllib3"
|
||||
version = "1.26.18"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
|
||||
files = [
|
||||
@ -1661,6 +1741,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
name = "virtualenv"
|
||||
version = "20.13.1"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
files = [
|
||||
@ -1682,6 +1763,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
|
||||
name = "vulture"
|
||||
version = "2.6"
|
||||
description = "Find dead code"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1696,6 +1778,7 @@ toml = "*"
|
||||
name = "wrapt"
|
||||
version = "1.16.0"
|
||||
description = "Module for decorators, wrappers and monkey patching."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
@ -1775,6 +1858,7 @@ files = [
|
||||
name = "yamllint"
|
||||
version = "1.26.3"
|
||||
description = "A linter for YAML files."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
@ -1790,6 +1874,7 @@ setuptools = "*"
|
||||
name = "zipp"
|
||||
version = "3.7.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
@ -1804,4 +1889,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8.1"
|
||||
content-hash = "30cec0ead12f35e594f7f69e789e0183aeb74394ab81c5b4e2c84de9929fc4b0"
|
||||
content-hash = "089373acc7cc350a441e8a935c011b76c1b31202a8972a42dc80bb8e2562c0e9"
|
||||
|
@ -27,7 +27,7 @@ dominate = "*"
|
||||
pyserial = "*"
|
||||
|
||||
## test requirements
|
||||
shamir-mnemonic = "^0.2.1"
|
||||
shamir-mnemonic = "^0.3.0"
|
||||
fido2 = "^0.8.0"
|
||||
python-bitcoinlib = "^0.11.0"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user