trezor / trezor-firmware

:lock: Trezor Firmware Monorepo
https://trezor.io
Other
1.36k stars 663 forks source link

Rethink ButtonRequests #35

Open mroz22 opened 5 years ago

mroz22 commented 5 years ago

Hi guys,

I might have found some inconsistencies worth refactoring, see:

ResetDevice call

BackupDevice call

I would suggest to change it this way:

ResetDevice call

BackupDevice call

tsusanka commented 5 years ago

We have discussed this with @mroz22 and we've concluded this needs more love. Also, this is a breaking change, so it makes sense to fix all inaccuracies at the same time. In particular, we need to:

  1. discuss what backup_device and reset_device should return
  2. investigate all other places if ButtonRequestType makes sense

Because this needs cooperation from the wallet team, I'm afraid this will need some meeting to discuss this. Therefore I'm moving this to backlog for now

prusnak commented 5 years ago

Let's also fix the case mentioned in https://github.com/trezor/trezor-firmware/issues/18

tsusanka commented 5 years ago

@TomasZorvan also encountered this. I'm setting higher priority because of that.

tsusanka commented 5 years ago

We will:

And we might make it a bit smarter by sending it directly in ui.Layout - tracked in #643

prusnak commented 5 years ago

Removed the ButtonRequest.data field in https://github.com/trezor/trezor-firmware/pull/644

matejcik commented 4 years ago

on TT it would be useful to have a distinct (set of?) ButtonRequest codes that precede PIN entry and possibly passphrase entry. This could be used to automate PIN entry in unit tests.

matejcik commented 4 years ago

I'm currently thinking about "semantic ButtonRequests" that we talked about at some points.

From the layouting thing, ISTM the solution is to extend the ButtonRequest so:

message ButtonRequest {
    optional ButtonRequestType code = 1;  // technically deprecated but we probably want to keep this?
    required string type = 2;
    repeated KeyValue content = 3;

    message KeyValue {
        required string key = 1;
        optional string value = 2;
    }
}

IOW, reduce every layout to a set of string keys and string (only!) values. in addition, a string (not enum) for "type", so that this is easily extensible and can be more descriptive.

no further structuring. let's review the other possible types:

example:

{
    type: "bitcoin_confirm_tx",
    content: [
        { key: "output_address", value: "DTjR1yfjefmWvcdA8M8T5YcyzToWdiUP29" },
        { key: "output_amount", value: "3.1415 DOGE" },
        { key: "output_address", value: "D6eudUhUhXpG2uH8UtQzEogug1gQtXYHzr" },
        { key: "output_amount", value: "40000 DOGE" },
        { key: "change_path", value: "m/44'/3'/0'/1/0" },
        { key: "change_amount", value: "420.69 DOGE" },
        { key: "total", value: "40523.8315 DOGE" },
        { key: "fee", value: "100 DOGE" },
    ]
}
tsusanka commented 4 years ago

That looks great! Some quick remarks from @szymonlesisz or @mroz22?

mmilata commented 4 years ago

Got a few questions:

What would be the compatibility guarantees of type and content? Would wallets need to consider their values only as a hint that they should not rely on?

Since this will be used internally by the firmware apps is there a way to make it hard to include secret information in content, such as mnemonic words or PIN matrix?

Would it make sense for type to be just another key in content? I'm thinking of requests like { key: "type", value: "confirm_tx" }, { key: "coin", value: "bitcoin" }, ....

matejcik commented 4 years ago

What would be the compatibility guarantees of type and content? Would wallets need to consider their values only as a hint that they should not rely on?

I'd consider them as parallel to the UI itself; for minor layout changes these would stay the same, for restructuring these would change.

As for type, ISTM the hardest question is coming up with a naming scheme that's as predictable as possible, so that an UI reflow doesn't break simply on rewords. OTOH perhaps type should consistently describe content?

Would it make sense for type to be just another key in content?

That doesn't seem useful? type is the one thing you always want to send, even if the content is empty. Having a separate field enforces this, and it saves space for the string "type" which would otherwise always need to be sent.

Since this will be used internally by the firmware apps is there a way to make it hard to include secret information in content, such as mnemonic words or PIN matrix?

This is a big one for discussion. We could use a naming convention (sensitive_foo), or some sort of naming trick ($foobar ?), maybe use static typechecking so that a value can't be sent until it is explicitly cleared?

tsusanka commented 4 years ago

As discussed on today's meeting we would like to add progress_step and progress_total. Those value inform the Host how many pages (not only related to Pagination but in general, how many screens will occur) in this workflow.


So for example during a Doge transaction signing you will encounter this screen:

Confirm transaction

Send 3.1415 DOGE to
DTjR1yfjefmWvcdA8M8T5YcyzToWdiUP29

and you will receive this ButtonRequest:

{
    type: "doge_confirm_output",
        progress_step: 1,
        progress_total: 2,
    content: [
        { key: "output_address", value: "DTjR1yfjefmWvcdA8M8T5YcyzToWdiUP29" },
        { key: "output_amount", value: "3.1415 DOGE" }
    ]
}

On a next screen you will see:

Confirm transaction

Spend 3.1515 DOGE
including 0.01 DOGE fee.

and will receive this ButtonRequest:

{
    type: "doge_confirm_tx",
        progress_step: 2,
        progress_total: 2,
    content: [
        { key: "fee", value: "0.01" },
        { key: "output_amount", value: "3.1415 DOGE" }
    ]
}
matejcik commented 4 years ago

during a Doge transaction signing

I think the type should be defined not by the selected coin but by the implementing code. This is implemented by the Bitcoin app so the type will be bitcoin_confirm_output and bitcoin_confirm_tx

matejcik commented 4 years ago

@mmilata says:

I'm not really sure how to incorporate the requirement that ButtonRequests provide progress_step and progress_total. Tracking the steps manually in every handler would be very messy.

ISTM that "progress steps" and "number of ButtonRequests" is not necessarily the same thing. Cross-posting into this issue for @mroz22 and @szymonlesisz to comment.

There's several ways we could look at this. The most simplistic one is to say that progress_total is the number of screens that will be shown in the current flow, and progress_step is the current step number. This does not seem correct. GetAddress(show_display=True) is the obvious case: the user can flip between address and QR code as long as they want. This should be the same "progress step" regardless of how many times they do it.

Another option is to say that progress_step is a fixed property of a particular dialog. For example, when in ResetDevice flow the user is asked for PIN, that's progress step 2; if the user does not want to set PIN, step 2 is skipped and the device moves on to step 3. This makes sense for flows that do not have variable number of steps. It's problematic for e.g. Bitcoin signing where each output is a step; or for mnemonic backup/recovery, where each word is a step.

What currently seems best to me is to consider progress_total and progress_step as local properties of a particular layout type. IOW: signing flow consists of a number of phases: "verify inputs (optional)", "verify outputs", "confirm total". Each of those can have its own progress_total: number of inputs, number of outputs, always a single step for "confirm total".

Suite would need to base its progress dialogs primarily off types, with progress providing additional data. Does that make sense from your POV?

mmilata commented 4 years ago

Also, IIRC we talked about pageable layouts having progress_total equal to the number of pages (edit: and send ButtonRequest on every scroll). This would make progress_total depend on the content and thus being quite variable.

ISTM that "progress steps" and "number of ButtonRequests" is not necessarily the same thing.

Yeah, I suppose that can't work. Could we say that within a particular handler invocation _progress_total never changes and progress_step never decreases_?

matejcik commented 4 years ago

_progress_step never decreases_?

if you're paging back and forth, i'd expect progress_step to go back too

andrewkozlik commented 3 years ago

As discussed on today's meeting we would like to add progress_step and progress_total. Those value inform the Host how many pages (not only related to Pagination but in general, how many screens will occur) in this workflow.

Let's make sure that the solution can be used to fix https://github.com/trezor/trezor-firmware/issues/1631#issuecomment-848911956. If Suite loads during Shamir recovery, the RecoveryDevice message it sends to Trezor resets the workflow, meaning that if the user is in the middle of entering a share, they have to start entering the share again. This seems to happen even if Suite is running before Trezor is plugged in, because it takes a while for Suite and Trezor to connect and in the meantime the user may have entered a few words already. See video in https://github.com/trezor/trezor-firmware/issues/1631#issuecomment-848911956. As I understand it, resetting the flow in Trezor is required so that Suite will be able to count the button requests and show recovery progress. The necessary information should be communicated in the ButtonRequest so that Trezor can hook the RecoveryDevice message into the ongoing recovery workflow without restarting it.

matejcik commented 3 years ago

In #1671 I am adding page and pages fields to the ButtonRequest, indicating (a) whether the screen is paginated or not, and (b) which page the user is currently on.

IIUC this isn't currently a requirement for Suite, but it's extremely useful for device tests. Also worth noting, Monero's naive_pagination is already implementing this behavior, presumably Monero GUI is using it.


per this proposal, we won't be implementing any sort of global progress counters, instead these will be superseded by ButtonRequest.name string identifiers of individual screens.

It is also not a requirement to have a generic key-value map, as proposed above.

Instead, I'm thinking that we should keep an index of the current screen -- e.g., if there are multiple outputs, the following could be sent:

{ name: "confirm_amount", index: 0 }
{ name: "confirm_destination", index: 0, page: 1, pages: 2 }
{ name: "confirm_destination", index: 0, page: 2, pages: 2 }
{ name: "confirm_amount", index: 1 }
{ name: "confirm_destination", index: 1, page: 1, pages: 2 }
{ name: "confirm_destination", index: 1, page: 2, pages: 2 }

This might get complicated, e.g., in EOS, where you can send multiple actions per transaction, each of which could have multiple outputs.

Or in SuperShamir backup, where you have multiple shares from multiple groups.

Two possible solutions:

(a) the index is global: confirm_output(7) identifies output 3 of group 2, and we presume that both host and device know the size of the groups, so this can be counted correctly.

(b) we add group and the output above becomes confirm_output(index=3, group=2). The above cases are well covered by one level of nesting, and I don't think we currently need more. If we ever did, however, this becomes unwieldy.

(In SuperShamir recovery, you have (1) current word of (2) n-th share of (3) n-th group. However, the process is agnostic about which share of which group you are entering, so index would only be the number of the word of the current share.)

matejcik commented 3 years ago

I created a prototype in https://github.com/trezor/trezor-firmware/compare/matejcik/buttonrequests?expand=1.

Suite folks, please take a look. The testcase diff is probably most interesting for you: matejcik/buttonrequests?expand=1#diff-990799ff40

I'm not happy about the naming convention, and I'd love some actual convention. Something like type:what? bitcoin:destination, bitcoin:total, bitcoin:warn:fee_over_threshold ?

(but then the "bitcoin:" part could be a separate field in protobuf, and for that matter "warn" could also be one of an enum, say: Warning, Error, Success, Show, Confirm? This of course depends on whether anyone actually cares about this information.)

matejkriz commented 2 years ago

Notes from testing the prototype in Suite:

matejkriz commented 2 years ago

meeting notes:

grdddj commented 2 years ago

One finding, possibly worth discussing/fixing - at the end of backup process, when randomly asked to confirm three words, confirm_word() is called, which does not send any ButtonRequests.

We may want to send button requests together with the index of word (0, 1, 2)

grdddj commented 2 years ago

Same situation as above (not sending ButtonRequests even if we maybe wanted to) happens in the recovery process - request_word() is called, which just sends a Keyboard object.

Therefore, no indexes are being sent while doing recovery

hynek-jina commented 2 years ago

Waiting for feedback from Suite

Hannsek commented 1 year ago

We discussed this today from the Send flow perspective. We need to know what exactly Suite (@Hermez-cz, @matejkriz) and Mobile (@mnuky, @Nodonisko) need. Can you tell?

Hermez-cz commented 1 year ago

In Suite we'd ideally like to know about every on-device user action separately. Has the user confirmed? Unconfirmed? Scrolled/paged forward? Scrolled/paged back? Opened additional information (on TT)? clicked anything else? Let Suite ideally know, so we can respond synchronously and provide user with exact state updates and contextual information.