1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-01-03 12:00:59 +00:00

feat(core): Implement entropy check workflow in ResetDevice.

This commit is contained in:
Andrew Kozlik 2024-09-04 17:01:01 +02:00 committed by matejcik
parent b9b36ef737
commit b8462580a2
8 changed files with 164 additions and 55 deletions

View File

@ -0,0 +1 @@
Entropy check workflow in ResetDevice.

View File

@ -6,9 +6,13 @@ if TYPE_CHECKING:
from trezor.messages import GetPublicKey, PublicKey
from trezor.protobuf import MessageType
from apps.common.keychain import Keychain
async def get_public_key(
msg: GetPublicKey, auth_msg: MessageType | None = None
msg: GetPublicKey,
auth_msg: MessageType | None = None,
keychain: Keychain | None = None,
) -> PublicKey:
from trezor import TR, wire
from trezor.enums import InputScriptType
@ -34,6 +38,7 @@ async def get_public_key(
if auth_msg.address_n != address_n[: len(auth_msg.address_n)]:
raise FORBIDDEN_KEY_PATH
if not keychain:
keychain = await get_keychain(curve_name, [paths.AlwaysMatchingSchema])
node = keychain.derive(address_n)

View File

@ -32,8 +32,12 @@ def is_bip39() -> bool:
return get_type() == BackupType.Bip39
def get_seed(passphrase: str = "", progress_bar: bool = True) -> bytes:
mnemonic_secret = get_secret()
def get_seed(
passphrase: str = "",
progress_bar: bool = True,
mnemonic_secret: bytes | None = None,
) -> bytes:
mnemonic_secret = mnemonic_secret or get_secret()
if mnemonic_secret is None:
raise ValueError # Mnemonic not set

View File

@ -62,10 +62,10 @@ async def load_device(msg: LoadDevice) -> Success:
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_backup_type(backup_type)
storage_device.set_passphrase_enabled(bool(msg.passphrase_protection))
storage_device.set_label(msg.label or "")
if msg.pin:

View File

@ -212,9 +212,8 @@ async def _finish_recovery(secret: bytes, backup_type: BackupType) -> Success:
if backup_type is None:
raise RuntimeError
storage_device.store_mnemonic_secret(
secret, backup_type, needs_backup=False, no_backup=False
)
storage_device.store_mnemonic_secret(secret, needs_backup=False, no_backup=False)
storage_device.set_backup_type(backup_type)
if backup_types.is_slip39_backup_type(backup_type):
if not backup_types.is_extendable_backup_type(backup_type):
identifier = storage_recovery.get_slip39_identifier()

View File

@ -3,8 +3,8 @@ from typing import TYPE_CHECKING, Sequence
import storage
import storage.device as storage_device
from trezor import TR
from trezor.crypto import slip39
from trezor.enums import BackupType
from trezor.crypto import hmac, slip39
from trezor.enums import BackupType, MessageType
from trezor.ui.layouts import confirm_action
from trezor.wire import ProcessError
@ -63,33 +63,52 @@ async def reset_device(msg: ResetDevice) -> Success:
# wipe storage to make sure the device is in a clear state
storage.reset()
# Check backup type, perform type-specific handling
if backup_types.is_slip39_backup_type(backup_type):
# set SLIP39 parameters
storage_device.set_slip39_iteration_exponent(slip39.DEFAULT_ITERATION_EXPONENT)
elif backup_type != BAK_T_BIP39:
# Unknown backup type.
raise RuntimeError
storage_device.set_backup_type(backup_type)
# request and set new PIN
if msg.pin_protection:
newpin = await request_pin_confirm()
if not config.change_pin("", newpin, None, None):
raise ProcessError("Failed to set PIN")
# generate and display internal entropy
prev_int_entropy = None
while True:
# generate internal entropy
int_entropy = random.bytes(32, True)
if __debug__:
storage.debug.reset_internal_entropy = int_entropy
entropy_commitment = (
hmac(hmac.SHA256, int_entropy, b"").digest() if msg.entropy_check else None
)
# request external entropy and compute the master secret
entropy_ack = await call(EntropyRequest(), EntropyAck)
entropy_ack = await call(
EntropyRequest(
entropy_commitment=entropy_commitment, prev_entropy=prev_int_entropy
),
EntropyAck,
)
ext_entropy = entropy_ack.entropy
# For SLIP-39 this is the Encrypted Master Secret
secret = _compute_secret_from_entropy(int_entropy, ext_entropy, msg.strength)
# Check backup type, perform type-specific handling
if backup_type == BAK_T_BIP39:
# in BIP-39 we store mnemonic string instead of the secret
secret = bip39.from_data(secret).encode()
elif backup_types.is_slip39_backup_type(backup_type):
# generate and set SLIP39 parameters
storage_device.set_slip39_iteration_exponent(slip39.DEFAULT_ITERATION_EXPONENT)
else:
# Unknown backup type.
raise RuntimeError
if not msg.entropy_check or not await _entropy_check(secret):
break
prev_int_entropy = int_entropy
# If either of skip_backup or no_backup is specified, we are not doing backup now.
# Otherwise, we try to do it.
@ -112,7 +131,6 @@ async def reset_device(msg: ResetDevice) -> Success:
storage_device.set_passphrase_enabled(bool(msg.passphrase_protection))
storage_device.store_mnemonic_secret(
secret, # for SLIP-39, this is the EMS
backup_type,
needs_backup=not perform_backup,
no_backup=bool(msg.no_backup),
)
@ -124,6 +142,44 @@ async def reset_device(msg: ResetDevice) -> Success:
return Success(message="Initialized")
async def _entropy_check(secret: bytes) -> bool:
"""Returns True to indicate that entropy check loop should continue."""
from trezor.messages import GetPublicKey, Success
from trezor.wire.context import call_any
from apps.bitcoin.get_public_key import get_public_key
from apps.common import coininfo, paths
from apps.common.keychain import Keychain
from apps.common.mnemonic import get_seed
seed = get_seed(mnemonic_secret=secret)
msg = Success()
while True:
req = await call_any(
msg,
MessageType.GetPublicKey,
MessageType.ResetDeviceContinue,
MessageType.ResetDeviceFinish,
)
assert req.MESSAGE_WIRE_TYPE is not None
if req.MESSAGE_WIRE_TYPE == MessageType.ResetDeviceContinue:
return True
if req.MESSAGE_WIRE_TYPE == MessageType.ResetDeviceFinish:
return False
assert GetPublicKey.is_type_of(req)
req.show_display = False
curve_name = (
req.ecdsa_curve_name
or coininfo.by_name(req.coin_name or "Bitcoin").curve_name
)
keychain = Keychain(seed, curve_name, [paths.AlwaysMatchingSchema])
msg = await get_public_key(req, keychain=keychain)
async def _backup_bip39(mnemonic: str) -> None:
words = mnemonic.split()
await layout.show_backup_intro(single_share=True, num_of_words=len(words))
@ -272,7 +328,7 @@ def _validate_reset_device(msg: ResetDevice) -> None:
def _compute_secret_from_entropy(
int_entropy: bytes, ext_entropy: bytes, strength_in_bytes: int
int_entropy: bytes, ext_entropy: bytes, strength_bits: int
) -> bytes:
from trezor.crypto import hashlib
@ -282,7 +338,7 @@ def _compute_secret_from_entropy(
ehash.update(ext_entropy)
entropy = ehash.digest()
# take a required number of bytes
strength = strength_in_bytes // 8
strength = strength_bits // 8
secret = entropy[:strength]
return secret

View File

@ -178,13 +178,11 @@ def set_homescreen(homescreen: bytes) -> None:
def store_mnemonic_secret(
secret: bytes,
backup_type: BackupType,
needs_backup: bool = False,
no_backup: bool = False,
) -> None:
set_version(common.STORAGE_VERSION_CURRENT)
common.set(_NAMESPACE, _MNEMONIC_SECRET, secret)
common.set_uint8(_NAMESPACE, _BACKUP_TYPE, backup_type)
common.set_true_or_delete(_NAMESPACE, _NO_BACKUP, no_backup)
common.set_bool(_NAMESPACE, INITIALIZED, True, public=True)
if not no_backup:

View File

@ -239,35 +239,81 @@ example, by using PKCS7. See
## ResetDevice
Reset device message performs Trezor device
setup and generates new wallet with new recovery
seed. The device must be in unitialized
state, the firmware is already installed but it has not been initialized
yet. If it is initialized and the user wants to perform a reset device,
The ResetDevice message performs Trezor device
setup and generates a new wallet with a new recovery
seed. The device must be in unitialized state, meaning that
the firmware is already installed but it has not been initialized
yet. If it is initialized and the user wants to perform a device reset,
the device must be wiped first. If the Trezor is prepared for its
initialization the screen is showing "Go to trezor.io". The reset device
can be done in Trezor Wallet interface (https://trezor.io/start) and
also with Python trezorctl command. After sending
message to the device, device warn us to never make a digital copy of
your recovery seed and never upload it online, this message has to be
confirmed by pressing on "I understand" on the device. After confirmed,
the device produces internal entropy which is random of 32 bytes,
requests external entropy which is produced in computer and computes
mnemonic (recovery seed) using internal, external entropy and given
strength (12, 18 or 24 words). Trezor Wallet
interface doesn't provide option to choose how many words there should
initialization, the screen is showing "Go to trezor.io". The device reset
can be done in the Trezor Suite interface (https://trezor.io/start) or
using Python trezorctl command. After sending the ResetDevice
message to the device, the device warns the user to never make a digital copy
of their recovery seed and never upload it online, this message has to be
confirmed by pressing "I understand" on the device. After confirmation,
the device produces internal entropy which is a random value of 32 bytes,
requests external entropy which is produced in the host computer and computes
the mnemonic (recovery seed) using internal, external entropy and the given
strength (12, 18 or 24 words). Trezor Suite
interface doesn't provide an option to choose how many words there should
be in the generated mnemonic (recovery seed). It is hardcoded to 12
words for Trezor Model T but if done with python's trezorctl command it
can be chosen (for initialization with python's trezorctl command, 24
words mnemonic is default). After showing mnemonic on the Trezor device,
Trezor Model T requires 2 random words to
be entered to the device to confirm the user has written down the
mnemonic properly. If there are errors in entered words, the device
Trezor Model T requires the user to enter several words at random positions
in the mnemonic to confirm that the user has written down the
mnemonic properly. If there are errors in the entered words, the device
shows the recovery seed again. If the backup check is successful, the
setup is finished. If the Trezor Wallet interface is used, user is asked
to set the label and pin (setting up the pin can be skipped) for the
setup is finished. If the Trezor Wallet interface is used, the user is asked
to set the label and PIN (setting up the PIN can be skipped) for the
wallet, this is optional when using python trezorctl command.
The ResetDevice command supports two types of workflows.
### Simple ResetDevice workflow
1. H -> T `ResetDevice` (Host specifies strength, backup type, etc.)
2. H <- T `EntropyRequest` (No parameters.)
3. H -> T `EntropyAck` (Host provides external entropy.)
4. H <- T `Success`
### Entropy check workflow
The purpose of this workflow is for the host to verify that when Trezor
generates the seed, it correctly includes the external entropy from the host.
The host performs a randomized test asking Trezor to generate several seeds,
checking that they were generated correctly and using the last one as the final
seed. The workflow is triggered by setting `ResetDevice.entropy_check` to true.
The host chooses a small random number *n*, e.g. from 1 to 5, and proceeds as follows:
1. H -> T `ResetDevice` (Host specifies strength, backup type, etc.)
2. H <- T `EntropyRequest` (Trezor commits to an internal entropy value.)
3. H -> T `EntropyAck` (Host provides external entropy.)
4. H <- T `Success` (Trezor stores the seed in storage cache.)
5. Host obtains the XPUBs for several accounts that the user intends to use:
1. H -> T `GetPublicKey`
2. H <- T `PublicKey`
6. If this step was executed less than *n* times, then:
1. H -> T `ResetDeviceContinue` (Host instructs Trezor to prove seed correctness.)
2. H <- T `EntropyRequest` (Trezor reveals previous internal entropy and commits to a new internal entropy value.)
3. The host verifies that the entropy commitment is valid, derives the seed and checks that it produces the same XPUBs as Trezor provided in step 5.
4. Go to step 3.
7. Host instructs trezor to store the current seed in flash memory.
1. H <- T `ResetDeviceFinish`
2. H -> T `Success`
The host should record the XPUBs that it received in the last repetition of
step 5. Every time the user connects the Trezor to the host, it should verify
that the XPUBs for the given accounts remain the same in order to prevent a
fake malicious Trezor from changing the seed.
The purpose of Trezor's commitment to internal entropy is to enforce that
Trezor chooses its internal entropy before the host provides the external
entropy. This ensures that Trezor cannot choose its internal entropy based on
the external entropy and manipulate the value of the resulting seed. The
commitment is computed as
`entropy_commitment=HMAC-SHA256(key=internal_entropy, msg="")`.
## RecoveryDevice
Recovery device lets user to recover BIP39 seed into