feat(core): Support extendable backup flag in SLIP-39.

Andrew Kozlik 2 weeks ago
parent d6b352befe
commit e329377b6c

@ -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,7 @@ 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 +61,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(

@ -122,6 +122,10 @@ async def _finish_recovery_dry_run(secret: bytes, backup_type: BackupType) -> Su
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 +153,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()

@ -47,7 +47,7 @@ def process_slip39(words: str) -> tuple[bytes | None, slip39.Share]:
# 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 +85,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

@ -75,6 +75,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.
@ -120,6 +121,7 @@ async def _backup_slip39_basic(encrypted_master_secret: bytes) -> None:
threshold = await layout.slip39_prompt_threshold(shares_count)
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
@ -129,6 +131,7 @@ async def _backup_slip39_basic(encrypted_master_secret: bytes) -> None:
1, # Single Group threshold
[(threshold, shares_count)], # Single Group threshold/count
identifier,
extendable,
iteration_exponent,
encrypted_master_secret,
)[0]
@ -156,6 +159,7 @@ async def _backup_slip39_advanced(encrypted_master_secret: bytes) -> None:
groups.append((share_threshold, share_count))
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
@ -165,6 +169,7 @@ async def _backup_slip39_advanced(encrypted_master_secret: bytes) -> None:
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.

@ -56,6 +56,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,13 @@ 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(4)
"""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)
@ -111,6 +114,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 +125,7 @@ class Share:
def __init__(
self,
identifier: int,
extendable: bool,
iteration_exponent: int,
group_index: int,
group_threshold: int,
@ -130,6 +135,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 +150,7 @@ def decrypt(
passphrase: bytes,
iteration_exponent: int,
identifier: int,
extendable: bool,
progress_callback: Callable[[int, int], None] | None = None,
) -> bytes:
"""
@ -154,7 +161,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,
@ -176,6 +183,7 @@ def split_ems(
group_threshold: int, # The number of groups required to reconstruct the master secret.
groups: list[tuple[int, int]], # A list 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]]:
@ -216,6 +224,7 @@ def split_ems(
group_mnemonics.append(
_encode_mnemonic(
identifier,
extendable,
iteration_exponent,
group_index,
group_threshold,
@ -229,11 +238,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:
@ -241,6 +250,7 @@ def recover_ems(mnemonics: list[str]) -> tuple[int, int, bytes]:
(
identifier,
extendable,
iteration_exponent,
group_threshold,
_group_count,
@ -264,7 +274,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:
@ -285,7 +295,8 @@ def decode_mnemonic(mnemonic: str) -> Share:
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
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)
tmp = _int_from_indices(
mnemonic_data[_ID_EXP_LENGTH_WORDS : _ID_EXP_LENGTH_WORDS + 2]
@ -312,6 +323,7 @@ def decode_mnemonic(mnemonic: str) -> Share:
return Share(
identifier,
extendable,
iteration_exponent,
group_index,
group_threshold + 1,
@ -407,11 +419,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(
_bits_to_bytes(_ID_LENGTH_BITS), "big"
)
def _get_salt(identifier: int, extendable: bool) -> bytes:
salt = _CUSTOMIZATION_STRING
if not extendable:
salt += identifier.to_bytes(_bits_to_bytes(_ID_LENGTH_BITS), "big")
return salt
def _create_digest(random_data: bytes, shared_secret: bytes) -> bytes:
from trezor.crypto import hmac
@ -479,12 +491,13 @@ 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),
)
@ -492,6 +505,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.
@ -511,7 +525,7 @@ 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)
@ -527,8 +541,9 @@ def _encode_mnemonic(
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()
@ -538,6 +553,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)
@ -548,7 +564,7 @@ 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."
)
@ -571,6 +587,7 @@ def _decode_mnemonics(
return (
identifiers.pop(),
extendable_flags.pop(),
iteration_exponents.pop(),
group_thresholds.pop(),
group_counts.pop(),

@ -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,7 +30,7 @@ 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)
mnemonics = slip39.split_ems(1, [(3, 5)], identifier, False, 1, ems)
mnemonics = mnemonics[0]
self.assertEqual(
slip39.recover_ems(mnemonics[:3]), slip39.recover_ems(mnemonics[2:])
@ -38,9 +38,9 @@ class TestCryptoSlip39(unittest.TestCase):
def test_basic_sharing_fixed(self):
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, False, 1, self.EMS)
mnemonics = mnemonics[0]
identifier, exponent, ems = slip39.recover_ems(mnemonics[:3])
identifier, extended, exponent, 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)

@ -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.
@ -75,8 +75,9 @@ 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`.
3. Store the default iteration exponent `1` as `_SLIP39_ITERATION_EXPONENT`.
4. The storage now contains all parameters required for seed derivation.
3. Store the default extendable backup flag `True` as `_SLIP39_EXTENDABLE`.
4. Store the default iteration exponent `1` as `_SLIP39_ITERATION_EXPONENT`.
5. The storage now contains all parameters required for seed derivation.
### Seed derivation
@ -101,5 +102,6 @@ This process does not use passphrase.
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.
5. Store the extendable backup flag as `_SLIP39_EXTENDABLE`.
6. Store the iteration exponent as `_SLIP39_ITERATION_EXPONENT`.
7. The storage now contains all parameters required for seed derivation.

Loading…
Cancel
Save