Passphrase Redesign In 1.9.0 / 2.3.0

On the T1, passphrase must be entered on the host PC and sent to Trezor. On the TT, the user can choose whether to enter the passphrase on host or on Trezor's touch screen.

In versions 1.9.0 and 2.3.0 we have redesigned how the passphrase is communicated between the Host and the Device. The new design is documented thoroughly in passphrase.md and this document should help with the transition from the old design.

New features

  • Passphrase flow is now identical for T1 and TT.
  • By keeping track of sessions, it is possible to avoid having to send passphrase repeatedly.
  • The choice between entering on Host or Device for TT has been moved from Device to Host.
  • Multiple passphrases are cached simultaneously.

Backwards compatibility

T1 1.9.0 is fully backwards-compatible and works with existing Host code.

TT 2.3.0 communicating with old Host software degrades to T1-level features: entering passphrase on device will not be available, and on-device passphrase caching via the state field will not be available.

As a workaround, it is possible to use the "passphrase always on device" feature on the new TT firmware. When enabled, the passphrase flow is completely hidden from the Host software, and the Device itself prompts the user to enter the passphrase.

Implementation guide

Protobuf changes

Protobuf has built-in backwards compatibility mechanisms, so a conforming implementation should continue to work with old protobuf definitions.

To restore support for TT on-device passphrase entry, and to make use of the new features, you will need to update to newer protobuf definitions from trezor-common (TODO: link to commit in trezor-common).

The gist of the changes is:

  • PassphraseRequest.on_device was deprecated, and renamed to _on_device. New Devices will never send this field.
  • Corresponding field PassphraseAck.on_device was added.
  • PassphraseAck.state was deprecated, and renamed to _state. It is retained for code compatibility, but the field should never be set.
  • PassphraseStateRequest/PassphraseStateAck messages were deprecated, and renamed with a Deprecated_ prefix. New Devices will not send or accept these messages.
  • Initialize.state was renamed to Initialize.session_id.
  • Corresponding field Features.session_id was added. New Devices will always send this field in response to Initialize call.
  • A new value Capability_PassphraseEntry was added to the Features.Capability enum. This capability will be sent from a Device that supports on-device passphrase entry (currently only TT).

Restoring on-device entry for TT

The Host software reacts to a PassphraseRequest message by prompting the user for a passphrase and sending it in the PassphraseAck.passphrase field.

A new UI element should be added: when the passphrase prompt is displayed on Host, there should be an option to "enter passphrase on device". When the user selects this option, the Host must send a PassphraseAck(passphrase=null, on_device=true).

The "enter passphrase on device" option should be displayed when Features.capabilities contain the Capability_PassphraseEntry value, regardless of reported Trezor version or model. Firmwares older than 2.3.0 or 1.9.0 never set this value, so this ensures forwards and backwards compatibility.

Cross-version compatibility for TT

TT version < 2.3.0 will send PassphraseRequest(_on_device=true) if the user selected on-device entry. Neither T1 nor TT >= 2.3.0 will ever set this field to true.

If the Host receives PassphraseRequest(_on_device=true), it should immediately respond with PassphraseAck() with no fields set.

TT version < 2.3.0 will send Deprecated_PassphraseStateRequest(state=[bytes]) after receiving PassphraseAck. The Host should immediately respond with Deprecated_PassphraseStateAck() with no fields set. If the Host does session management, it should store the value of state as the session ID.

Triggering passphrase prompt

Use GetAddress(coin_name="Testnet", address_n=[44'/1'/0'/0/0]) (the first address of the first account of Testnet) to ensure that the Device asks for a passphrase if needed, and caches it for future use.

Validating passphrases

You can store the result of the above call, and in the future, compare it to a newly received address. This is a good way to check if the user is using the same passphrase as last time.

Do not store user-entered passphrases for the purpose of validation, even in hashed, encrypted, or otherwise obfuscated format.

Session support

A call to Initialize can include a session_id field. When starting a new user session, this field should be left empty.

The response Features message will always include a session_id field. The value of this field should be stored. When calling Initialize again, the stored value should be sent as session_id. If the received Features.session_id is the same, it means that session was resumed successfully and the user will not be prompted for passphrase.

--> Initialize()
<-- Features(session_id=0xABCDEF, ...)

--> Initialize(session_id=0xABCDEF)
<-- Features(session_id=0xABCDEF)
# (session resumed successfully)

--> Initialize(session_id=0xABCDEF)
<-- Features(session_id=0x123456)
# (session was not resumed, user will be prompted for passphrase again)

Session support is identical on T1 and TT, and both models support multiple sessions, i.e., it is possible to seamlessly switch between using multiple passphrases.

Cross-version compatible algorithm summary

The following algorithm will ensure that your Host application works properly with both T1 and TT with both older and newer firmwares.

  1. If you have a session ID stored, call Initialize(session_id=stored_session_id)
  2. Check the value of Features.session_id. If it is identical to stored_session_id, the session was resumed and user will not need to be prompted for a passphrase.
    1. If Features.session_id is not set, you are communicating with an older Device. Do not store the null value as session ID.
    2. Otherwise store the value as stored_session_id.
  3. When you receive a PassphraseRequest(_on_device=true), respond with PassphraseAck() with no fields set.
  4. When you receive a PassphraseRequest, prompt the user for passphrase.
    1. If Features.capabilities contains value Capability_PassphraseEntry, display a UI element that allows the user to enter passphrase on-device.
    2. If the user chooses this option, send PassphraseAck(passphrase=null, on_device=true)
    3. If the user enters the passphrase in your application, send PassphraseAck(passphrase="user entered passphrase", on_device=false)
  5. When you receive a Deprecated_PassphraseStateRequest(state=...), store the value of state as stored_session_id, and respond with Deprecated_PassphraseStateAck with no fields set.

Note: up to 64 bytes may be required to store the session ID. Firmwares < 2.3.0 use a 64-byte value, newer firmwares use a 32-byte value.