Use of SLIP-39 in trezor-core

SLIP-39 describes a way to securely back up a secret value using Shamir's Secret Sharing scheme.

The secret value, called a Master Secret (MS) in SLIP-39 terminology, is first encrypted by a passphrase, producing an Encrypted Master Secret (EMS). The EMS is then split into a number of shares, which are encoded as a set of mnemonic words. Afterwards, it is possible to recombine some or all of the shares to obtain back the EMS, and when the correct passphrase is provided, decrypt the original Master Secret.

This does not quite match Trezor's use of the "passphrase protection" feature, namely that any passphrase is valid, and using any passphrase will yield a working wallet.

SLIP-39 enables this usage by specifying that passphrases are not validated in any way. Decrypting an EMS with any passphrase will produce data usable as the Master Secret, regardless of whether it is the original data or not.

Seed handling in Trezor

Trezor stores a mnemonic secret in a storage field _MNEMONIC_SECRET. This is the input for the root node derivation process: mnemonic.get_seed(passphrase) takes the user-provided passphrase as an argument, and derives the appropriate root node from the mnemonic secret.

With BIP-39, the recovery phrase itself is the mnemonic secret. During device initialization, the raw recovery phrase is given to the user, and also directly stored in the _MNEMONIC_SECRET field. Whenever the root node is required, it is derived by 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.

SLIP-39 implementation

The reference implementation of SLIP-39 provides the following high-level API:

  • generate_mnemonics(group parameters, master_secret, passphrase): Encrypt Master 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): 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): Decrypt the secret with the given passphrase and parameters, to obtain the original Master Secret.

Only the functions denoted in bold are implemented in trezor-core. Recovery shares are generated with split_ems and combined with recover_ems. Passphrase decryption is done with decrypt. There is never an original "master secret" to be encrypted, so the encrypt function is also omitted.

Step-by-step

Device initialization

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.

Seed derivation

This is the only process that uses passphrase.

  1. If passphrase is enabled, prompt user for passphrase. Otherwise use empty string.
  2. Use slip39.decrypt(_MNEMONIC_SECRET, passphrase, _SLIP39_ITERATION_EXPONENT, _SLIP39_IDENTIFIER) to "decrypt" the root node that matches the provided passphrase.

Seed backup

This process does not use passphrase.

  1. Prompt user for group parameters (number of groups, number of shares per group, etc.).
  2. Use slip39.split_ems(group parameters, _SLIP39_IDENTIFIER, _SLIP39_ITERATION_EXPONENT, _MNEMONIC_SECRET) to split the secret into the given number of shares.

Seed recovery

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.