Name the four things this application can do, in its menu - #157
Merged
Merged
Conversation
The menu listed formats -- BIP39 Check, SSKR Check, BIP85 Generate -- and
generating a backup was not among them. It was reachable from exactly one
place, check_result_callback(), and only when the tool was BIP39, the phrase
was well formed, and it matched the device:
if (tool_type == TOOL_TYPE_BIP39 && bip39_mnemonic_check(&seed_match) &&
seed_match) {
display_select_generate_sskr_page();
So splitting a phrase into shares meant opening BIP39 Check, typing it in,
succeeding, and accepting an offer nobody asked for. Rebuilding a phrase from
shares had the same shape behind SSKR Check. The two operations worth the most
in a backup tool were the two with no entry.
The four entries name what the user came to do, and a screen between the
backup entry and the keyboard says why the phrase is being asked for at all:
compare_recovery_phrase() gets a seed back from the device and never the
words, so the words have to come from the person.
A fourth entry is not a fourth tool_type, and the reason is not the display
code. compare_recovery_phrase() (src/common/common_seed.c) dispatches on
tool_type to choose which buffer to derive from; a fourth value falls through
both branches, hands 64 zero bytes to the comparison, and reports every phrase
as not matching -- with nothing on screen to say the derivation never ran. So
tool_type keeps three values and means "what was typed", and a separate
user_intent carries the four and means "what it is being typed for". Checking
a phrase and backing one up are the same tool and different intentions.
That retires the indexing the verdict screen used -- 1 + tool_type * 2 +
seed_match into a [2][5] table, bounded by a test naming the two safe values.
It is a table with one row per intention now, indexed by named constants,
sized on the enumeration and static-asserted against it. Adding a fifth
intention is two compile errors and three -Wswitch diagnostics, each on a line
that has to decide something. The row index is also bounded at run time, and
user_intent is volatile so that the bound survives: with the variable static
and every write a constant, the compiler proves the check and folds it away --
measured, _etext was byte-identical with the bound present and removed.
The verdict is a destination in the check flow and a step on the way in the
backup flow, so it does not say the same thing, and a failure least of all:
"doesn't match the one present on this Ledger device" answers "is this my
phrase?", not "can I back this up?". Only the screen that continues reads "Tap
to continue".
Checking no longer offers to split: the offer duplicated a menu entry, and the
screen it stood on is a destination. The share-count keypad's back arrow now
leaves for the home page, which reaches reset_globals() in one gesture where
the offer took two.
The menu has no icon. On apex_p a fourth button occupies the space the icon
and title used; and the icons here name formats, so Generate and Recover would
both have taken icon_sskr -- which is what the BIP85 entry already wore.
The Nano menu is unchanged by this commit: nanox and nanos+ build byte for
byte as before, and nanos differs only in DWARF, which it alone carries.
Three of them, not four: BIP-85 has no BAGL screen on any Nano, so a fourth
entry would lead nowhere. That is the one place the two stacks genuinely
differ rather than merely word things differently.
The defect and the fix are the same as on the touch devices. Splitting a
phrase into shares was the third step of the BIP-39 match flow, so it required
opening Check BIP39, typing the phrase, succeeding, and finding an offer that
arrived unasked. It is an entry of the idle menu now, and a step between it
and the length list says why the phrase is being asked for -- which needs
saying more here than there, since those words are entered one letter at a
time with two buttons.
Check phrase Generate Recover
on this Ledger backup shares from backup
The last two say word for word what the touch buttons say. The first cannot: a
pbb step draws its two lines in an icon-flanked box 87px wide on the 128x32
Nano S, and "Check recovery" alone is 88px. What it does with the room it has
is worth more than the consistency it loses -- "on this Ledger" says what the
phrase is checked against, which the touch button has no room to say at all.
The two entries these replace ended on "recovery phrase", 93px against that
87px box, and had always been over it: one of the two strings
tests/unit/tests/ui_strings.c recorded as failing rather than asserting. All
six fragments here are asserted, and so are the five new ones on the two
screens the flow adds.
The verdict splits in two, as it did on the other stack. Checking ends on it,
and its flow lost the step that generated shares and gained a way back to the
menu; splitting passes through it and keeps that step. A mismatch in the
backup flow gets a second step -- "It would not restore this Ledger" -- because
a pbb title has two short lines and no room for the sentence the touch screen
carries in one.
The choice between the two flows lives next to them in ux_nano.c rather than
at the two call sites: nanos_enter_phrase.c and nanox_enter_phrase.c both
reach the verdict, by different routes, and a copy in each is a third thing to
keep in step. A _Static_assert there gives this stack the guarantee the other
one already had -- src/nbgl/ui.c is compiled into no Nano target, so its
assertions said nothing here, and a new intention would have taken the else
branch in silence.
nanos also gains a memzero that nanox_enter_phrase.c already had on the same
branch: after a BIP-39 mismatch nothing reads the phrase again, and this flow
now puts one more screen between the verdict and the erasure ui_idle_init()
performs. Only that branch -- the SSKR one keeps words_buffer, which is where
the reconstructed phrase is and what recover_bip39() displays.
The three touch binaries are byte for byte what the previous commit produced.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What you could not find, and now can
Backing up your recovery phrase.
Today it is reachable from exactly one place:
check_result_callback()insrc/nbgl/ui.coffers it, and only when the tool was BIP39, the phrase waswell formed, and it matched the device.
So to split a phrase into SSKR shares you have to open
BIP39 Check, type itin, succeed, and then accept an offer you did not ask for. Nothing in the menu
says any of that is there. Rebuilding a phrase from shares has the same shape,
behind a successful
SSKR Check. On the Nano devices it is the same defect:generating shares is the third step of the BIP-39 match flow.
The menu named formats, and the two operations worth the most in a backup tool
were the two with no entry.
The icon goes with the three-entry menu. Two reasons, both measured: on apex_p
the fourth button occupies the space the icon and title used, and the icons in
this repository name formats, so Generate and Recover would both have taken
icon_sskr— which is what the BIP85 entry already wore, identical to the SSKRone beside it.
The screen this adds
One, and it is on the path the user walks. Someone who picks
Generate backup sharesis about to be asked to type twenty-four words into a device thatalready holds them, and nothing said why.
compare_recovery_phrase()gets aseed back from the device and never the words, so the words have to come from
the person.
It takes the place of
Generate SSKR Phrase?, which sat after the verdict andasked whether to do a thing the user had not asked for.
What replaced the arithmetic on
tool_typeA fourth menu entry does not become a fourth
tool_type, and the reason is notthe display code.
compare_recovery_phrase()(src/common/common_seed.c) dispatches ontool_typeto decide which buffer to derive a seed from. A fourth value fallsthrough both of its branches, hands 64 zero bytes to the comparison, and
reports every phrase as not matching the device — with nothing on screen to say
the derivation never ran. That is worse than the failure the verdict screen's
own comment warns about, and it is not where that comment is looking.
So
tool_typekeeps its three values and means "what was typed". A separateuser_intent(src/constants.h) carries the four and means "what it is beingtyped for". Checking a phrase and backing one up are the same tool and
different intentions, which is exactly what the old enumeration could not
express.
That also retires the indexing the verdict screen used:
into a
[2][5]table, bounded by a test naming the two values that were safe.It is a table with one row per intention now, indexed by named constants and
sized on the enumeration.
Adding a fifth intention and compiling produces two compile errors and three
-Wswitchdiagnostics on the touch stack, and a third compile error on theNano stack — each on a line that has to decide something:
The row index is bounded at run time as well, and
user_intentisvolatileso that the bound survives compilation. It did not, at first: with the variable
staticand every write to it a constant, the compiler proves the comparisonand folds it away —
_etextwas byte-identical on all three touch targets withthe bound present and with it removed. What a bound is for is a byte that
changed without anyone writing it, which is the case that proof excludes. Same
reason
checkpointsisvolatileincompare_recovery_phrase_finish().How the verdict behaves in each flow
The verdict is a destination when you came to check a phrase and a step on the
way when you came to back one up, so it does not say the same thing — and a
failure says least the same thing of all.
…matches the one present on this Ledger device.This is the recovery phrase on this Ledger device. It can be split into shares.…doesn't match the one present on this Ledger device.You would be backing up a phrase this Ledger cannot recover.Tap to dismissTap to continue, and only on the matchdoesn't match the one present on this Ledger deviceis a complete answer to"is this my phrase?". It is half of one to "can I back this up?", where what
matters is that the shares about to be written down would restore something
this device cannot.
From there the flow is unchanged, reached without a single screen having
offered anything:
Checking no longer offers to split: the offer duplicated a menu entry, and the
screen it stood on is a destination. The share-count keypad's back arrow now
leaves for the home page, reaching
reset_globals()in one gesture where theoffer took two.
What the Nano menu becomes
Three entries, not four. BIP-85 has no BAGL screen on any Nano, so a fourth
would lead nowhere. This is the one place the two stacks genuinely differ.
The last two say word for word what the touch buttons say. The first cannot: a
pbbstep draws its two lines in an icon-flanked box 87px wide on the 128x32Nano S, and
Check recoveryalone is 88px. What it does with the room it hasis worth more than the consistency it loses —
on this Ledgersays what thephrase is checked against, which the touch button has no room to say at all.
The two entries these replace ended on
recovery phrase, 93px against that87px box, and had always been over it: one of the two strings the unit test
recorded as failing rather than asserting. All six new fragments are asserted,
and so are the five on the two screens the Nano flow gains.
A mismatch in the backup flow gets a second step,
It would not restore this Ledger, because apbbtitle has no room for the sentence the touch screencarries in one.
nanosalso gains amemzerothatnanox_enter_phrase.calready had on the same branch: after a BIP-39 mismatch nothing reads the
phrase again, and this flow now puts one more screen between the verdict and
the erasure
ui_idle_init()performs.Sizes
_etextand_ebssread witharm-none-eabi-nm. Nottext/data/bssfromsize: on the touch targets_install_parameterssits at a fixed address and.textruns to the end of that block, so those numbers are constants ratherthan measurements.
_etext_ebssc0d0a22820000c80c0debd08da7a144dc0debd08da7a144dc0df2adcda7a1b4ec0df2c28da7a1b12c0df1e28da7a1b12The 4 bytes of RAM on the three Nanos are the context field recording which
menu entry was chosen. Six targets, zero warnings.
The two commits are split by interface stack, which buys a claim that can be
checked with
sha256rather than by reading: after the first commitnanoxand
nanos+are byte-identical to the base —nanosdiffers only in DWARF,which it alone carries, and
--strip-debuggives the same hash with.text,.rodata,.dataand.bssidentical — and after the second the threetouch binaries are byte-identical to the first.
Verification
unit-tests-matrix.yml, reported separately:80/80 on each.
nanos+ 13/16.
clang-format --dry-run -Werrorclean on every touched file insrc/.than by reading: removing the
seed_matchrequirement fails exactly the testthat claims to cover it on the touch stack, and two tests on the Nano stack.
This repository's CI does not run the functional tests on pull requests from
a fork — workflow permissions are read-only there. That is a property of
where the branch lives rather than a fault in the change; the results above
were produced locally on the same six binaries these commits contain.
What is not held by a test
explanation step and its backup-mismatch step have never been rendered
anywhere. They are held only by the unit test that measures each fragment
against the layout's real pixel budget — which measures characters, not
pixels drawn. That test's metrics are the Nano S ones applied to all three
Nano devices: conservative, and therefore right, but by approximation.
text assertion, so wording is pinned and placement is not. The menu, the
explanation screen and the verdicts were checked by reading back the
coordinates Speculos reports; those checks are not automated.
tests it because nothing can reach it: that flow goes to
display_generic_review()and never asks for a verdict. Left empty ratherthan filled with a plausible sentence, so a path that did arrive there would
draw a title over an empty body instead of an answer about a comparison that
never happened.
Check recovery phrasedoes not say what it is checked against, andDerive with BIP85says nothing to someone who does not already know. Bothwould be the better for a line of their own, and there is no room: between
the back button and the fourth entry Flex has 96px, of which one title line
takes 44, and a subtitle wrapped to two lines was measured under Speculos
drawing its second line underneath the first button — which does not clip it,
it deletes it. The per-entry alternative does not fit either:
nbgl_layoutAddTouchableBar()makes an entry 94px on apex_p with a one-linesubtitle, and four come to 376px against 340px of usable height.
stacks:
ux_sskr_nomatch_flowends on the step that displays it, and thetouch side gates on reconstruction rather than on
seed_match. That isunchanged here and is presumably intended — rebuilding a phrase from its own
shares on a device holding a different seed is the point of the feature — but
it is the one place a secret is displayed without a match, and this change
promotes that path from an offer behind a verdict to a menu entry.
wordCandidates[]insrc/nbgl/ui.cis still not erased byreset_globals(), which clears the pointer array but not the buffer holdingthe suggested words. Pre-existing, untouched, named because this change walks
past it.