Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entropy check workflow in ResetDevice #4155

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion common/protob/messages-management.proto
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ message ResetDevice {
optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow
optional bool no_backup = 9; // indicate that no backup is going to be made
optional BackupType backup_type = 10 [default=Bip39]; // type of the mnemonic backup
optional bool entropy_check = 11; // run with entropy check protocol
}

/**
Expand All @@ -435,14 +436,31 @@ message BackupDevice {
* @next EntropyAck
*/
message EntropyRequest {
optional bytes entropy_commitment = 1; // HMAC-SHA256 of Trezor's internal entropy used in entropy check.
optional bytes prev_entropy = 2; // Trezor's internal entropy from the previous round of entropy check.
}

/**
* Request: Provide additional entropy for seed generation function
* @next Success
*/
message EntropyAck {
required bytes entropy = 1; // 256 bits (32 bytes) of random data
required bytes entropy = 1; // 256 bits (32 bytes) of the host's random data
}

/**
* Request: Ask Trezor to reveal its internal entropy.
* @next EntropyAck
*/
message ResetDeviceContinue {
}

/**
* Request: Last step in the entropy check protocol. Stores the generated seed.
* @start
* @next Success
*/
message ResetDeviceFinish {
}

/**
Expand Down
2 changes: 2 additions & 0 deletions common/protob/messages.proto
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ enum MessageType {
MessageType_BackupDevice = 34 [(bitcoin_only) = true, (wire_in) = true];
MessageType_EntropyRequest = 35 [(bitcoin_only) = true, (wire_out) = true];
MessageType_EntropyAck = 36 [(bitcoin_only) = true, (wire_in) = true];
MessageType_ResetDeviceContinue = 994 [(bitcoin_only) = true, (wire_in) = true];
MessageType_ResetDeviceFinish = 995 [(bitcoin_only) = true, (wire_in) = true];
MessageType_PassphraseRequest = 41 [(bitcoin_only) = true, (wire_out) = true];
MessageType_PassphraseAck = 42 [(bitcoin_only) = true, (wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
MessageType_RecoveryDevice = 45 [(bitcoin_only) = true, (wire_in) = true];
Expand Down
1 change: 1 addition & 0 deletions core/.changelog.d/4155.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Entropy check workflow in ResetDevice.
5 changes: 4 additions & 1 deletion core/src/apps/common/mnemonic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import TYPE_CHECKING

import storage.cache as storage_cache
import storage.device as storage_device
from trezor import utils

Expand All @@ -15,7 +16,9 @@ def get() -> tuple[bytes | None, BackupType]:


def get_secret() -> bytes | None:
return storage_device.get_mnemonic_secret()
return storage_device.get_mnemonic_secret() or storage_cache.get(
storage_cache.APP_STAGED_MNEMONIC_SECRET
)


def get_type() -> BackupType:
Expand Down
4 changes: 3 additions & 1 deletion core/src/apps/common/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ def clone(self) -> "Slip21Node":
async def derive_and_store_roots() -> None:
from trezor import wire

if not storage_device.is_initialized():
if not storage_device.is_initialized() and not storage_cache.is_set(
storage_cache.APP_STAGED_MNEMONIC_SECRET
):
raise wire.NotInitialized("Device is not initialized")

need_seed = not storage_cache.is_set(storage_cache.APP_COMMON_SEED)
Expand Down
2 changes: 1 addition & 1 deletion core/src/apps/debug/load_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 2 additions & 3 deletions core/src/apps/management/recovery_device/homescreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
106 changes: 82 additions & 24 deletions core/src/apps/management/reset_device/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import TYPE_CHECKING, Sequence

import storage
import storage.cache as storage_cache
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

Expand Down Expand Up @@ -63,33 +64,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
int_entropy = random.bytes(32, True)
if __debug__:
storage.debug.reset_internal_entropy = int_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

# request external entropy and compute the master secret
entropy_ack = await call(EntropyRequest(), 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)
entropy_commitment = (
hmac(hmac.SHA256, int_entropy, b"").digest() if msg.entropy_check else None
)

# 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
# request external entropy and compute the master secret
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)

if backup_type == BAK_T_BIP39:
# in BIP-39 we store mnemonic string instead of the secret
secret = bip39.from_data(secret).encode()

if not msg.entropy_check or not await _entropy_check(secret, backup_type):
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.
Expand All @@ -112,7 +132,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),
)
Expand All @@ -124,6 +143,45 @@ async def reset_device(msg: ResetDevice) -> Success:
return Success(message="Initialized")


async def _entropy_check(secret: bytes, backup_type: BackupType) -> bool:
"""Returns True to indicate that entropy check loop should continue."""
from trezor.messages import GetPublicKey, Success
from trezor.wire.context import call_any, get_context

from apps import workflow_handlers

try:
storage_cache.set(storage_cache.APP_STAGED_MNEMONIC_SECRET, secret)
storage_cache.start_session()
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

handler = workflow_handlers.find_registered_handler(
get_context().iface, req.MESSAGE_WIRE_TYPE
)
assert handler is not None
msg = await handler(req)
finally:
storage_cache.delete(storage_cache.APP_STAGED_MNEMONIC_SECRET)
storage_cache.end_current_session()


async def _backup_bip39(mnemonic: str) -> None:
words = mnemonic.split()
await layout.show_backup_intro(single_share=True, num_of_words=len(words))
Expand Down Expand Up @@ -272,7 +330,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

Expand All @@ -282,7 +340,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

Expand Down
2 changes: 2 additions & 0 deletions core/src/storage/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
APP_MISC_COSI_NONCE = const(4 | _SESSIONLESS_FLAG)
APP_MISC_COSI_COMMITMENT = const(5 | _SESSIONLESS_FLAG)
APP_RECOVERY_REPEATED_BACKUP_UNLOCKED = const(6 | _SESSIONLESS_FLAG)
APP_STAGED_MNEMONIC_SECRET = const(7 | _SESSIONLESS_FLAG)

# === Homescreen storage ===
# This does not logically belong to the "cache" functionality, but the cache module is
Expand Down Expand Up @@ -144,6 +145,7 @@ def __init__(self) -> None:
32, # APP_MISC_COSI_NONCE
32, # APP_MISC_COSI_COMMITMENT
0, # APP_RECOVERY_REPEATED_BACKUP_UNLOCKED
32, # APP_STAGED_MNEMONIC_SECRET
)
super().__init__()

Expand Down
2 changes: 0 additions & 2 deletions core/src/storage/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,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:
Expand Down
2 changes: 2 additions & 0 deletions core/src/trezor/enums/MessageType.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions core/src/trezor/enums/__init__.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions core/src/trezor/messages.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading