diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-25 12:46:42 +0100 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-25 12:46:42 +0100 |
| commit | 096b5a4d17c85fa5f87db3e72364e9c3506d1e64 (patch) | |
| tree | 9846544fe7d76d849df4a1ee2058f4847f674e7e /source/ui/PinView.mc | |
| parent | e0384274bbeab999279bdc35bdddbd8a3a451aeb (diff) | |
feat(pin): exactly 4 digits, entered on the 4th digit
The keypad has no OK key; the 4th digit submits, so a tap that also
arrives as START can no longer submit a partial PIN (the likely cause
of "Wrong PIN" before the code opened). PINs set by earlier versions
still unlock and are then replaced by a 4-digit one. Opening an entry
ignores a second request from the same tap. A Require PIN sent from
the phone while the watch is locked now applies at the next PIN
instead of waiting for Confirm all. The linter skips .claude.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/ui/PinView.mc')
| -rw-r--r-- | source/ui/PinView.mc | 87 |
1 files changed, 64 insertions, 23 deletions
diff --git a/source/ui/PinView.mc b/source/ui/PinView.mc index 4502919..fa58bfe 100644 --- a/source/ui/PinView.mc +++ b/source/ui/PinView.mc @@ -2,11 +2,18 @@ // Summary: PIN entry for unlocking, creating and changing the PIN: touch keypad or button digit selector, lockout display. // // README 13.1 and 13.4: -// * Touch screens get a 3x4 keypad (digits, delete, OK). Watches without a -// touch screen get a digit selector: UP/DOWN change the digit, START -// adds it (or confirms on "OK"), BACK deletes. +// * A PIN has exactly 4 digits (KeyManager.PIN_DIGITS) and is submitted +// as soon as the 4th is entered; there is no OK key. +// * Touch screens get a keypad (1-9, delete, 0). The START button does +// nothing there: a tap can also arrive as START, which used to submit a +// PIN early. Watches without a touch screen get a digit selector: +// UP/DOWN change the digit, START adds it, BACK deletes. +// * A legacy PIN (4 to 10 digits, set before the 4-digit rule) is entered +// the old way, with an OK key (and START on touch screens, "OK" in the +// selector); after it unlocks, the screen asks for a new 4-digit PIN +// and then continues to what was opened. // * Entered digits show as dots. A wrong PIN vibrates and shows any wait. -// * Creating or changing the PIN asks twice and warns below 6 digits. +// * Creating or changing the PIN asks twice. // * Key derivation (about 1 s) runs as a job on a timer, so the screen // shows "Checking..." and the watchdog never trips. // The typed digits are a ByteArray that is wiped after every attempt. @@ -23,18 +30,19 @@ class PinView extends WatchUi.View { static const MODE_CREATE = 1; static const MODE_CHANGE = 2; - // Selector positions 0-9 are digits; this one is "OK". + // Selector positions 0-9 are digits; this one is "OK" (legacy PINs only). static const SELECT_OK = 10; - // Keypad keys besides the digits. + // Keypad keys besides the digits (OK for legacy PINs only). static const KEY_DELETE = -1; static const KEY_OK = -2; - static const RECOMMENDED_DIGITS = 6; // Calibrate the PIN derivation to about this long on this watch. static const DERIVATION_TARGET_MS = 1000; static const CALIBRATION_SAMPLE = 200; var mode as Number; var busy as Boolean = false; + // Unlocking a legacy PIN: 4 to 10 digits, submitted with OK. + var legacy as Boolean = false; private var _onSuccess as Method() as Void or Null; private var _digits as ByteArray = []b; @@ -57,6 +65,7 @@ class PinView extends WatchUi.View { View.initialize(); mode = pinMode; _onSuccess = onSuccess; + legacy = pinMode == MODE_UNLOCK && AppState.theVault().isLegacyPin(); } function onShow() as Void { @@ -85,7 +94,8 @@ class PinView extends WatchUi.View { // ---- Input (called by PinDelegate) ---------------------------------- - // A keypad key: 0-9, KEY_DELETE or KEY_OK. + // A keypad key: 0-9, KEY_DELETE or KEY_OK (legacy PINs only). The + // PIN_DIGITS-th digit of a 4-digit PIN submits it. function press(key as Number) as Void { if (busy || waitSeconds() > 0) { return; @@ -93,17 +103,23 @@ class PinView extends WatchUi.View { if (key == KEY_DELETE) { deleteDigit(); } else if (key == KEY_OK) { - submit(); - } else if (_digits.size() < KeyManager.MAX_PIN_DIGITS) { + if (legacy) { + submit(); + } + } else if (_digits.size() < (legacy ? KeyManager.MAX_PIN_DIGITS : KeyManager.PIN_DIGITS)) { _digits.add(0x30 + key); _message = null; + if (!legacy && _digits.size() == KeyManager.PIN_DIGITS) { + submit(); + } } WatchUi.requestUpdate(); } - // Selector: UP/DOWN. + // Selector: UP/DOWN, over the digits (and "OK" for a legacy PIN). function cycle(delta as Number) as Void { - _selector = ((_selector + delta) % (SELECT_OK + 1) + SELECT_OK + 1) % (SELECT_OK + 1); + var positions = legacy ? SELECT_OK + 1 : SELECT_OK; + _selector = ((_selector + delta) % positions + positions) % positions; WatchUi.requestUpdate(); } @@ -146,19 +162,16 @@ class PinView extends WatchUi.View { // ---- Checking and setting the PIN ------------------------------------ + // A complete entry: check it (unlock) or keep it for the repeat (new PIN). private function submit() as Void { - if (!KeyManager.isValidPin(_digits)) { - showMessage("4 to 10 digits", true); - return; + if (!(legacy ? KeyManager.isValidPin(_digits) : KeyManager.isValidNewPin(_digits))) { + return; // a legacy OK with fewer than 4 digits } if (mode == MODE_UNLOCK) { startUnlock(); } else if (_first == null) { _first = _digits; _digits = []b; - if (_first.size() < RECOMMENDED_DIGITS) { - showMessage("6+ digits are safer", false); - } } else if (Bytes.equal(_first as ByteArray, _digits)) { startSetPin(); } else { @@ -181,6 +194,7 @@ class PinView extends WatchUi.View { clearDigits(); if (e.code == VaultError.NO_PIN) { mode = MODE_CREATE; + legacy = false; showMessage("Set a PIN first", false); } else if (e.code != VaultError.LOCKED_OUT) { showMessage("PIN data damaged", true); @@ -201,7 +215,7 @@ class PinView extends WatchUi.View { } catch (e instanceof VaultError) { Bytes.wipe(_jobPin); _jobPin = null; - showMessage("Unlock first", true); + showMessage(e.code == VaultError.LOCKED ? "Unlock first" : KeyManager.PIN_DIGITS + " digits", true); return; } startJob(method(:onSetDone)); @@ -225,8 +239,13 @@ class PinView extends WatchUi.View { if (vault.finishUnlock(_job as PinJob)) { _job = null; vault.promotePending(); + ChangeQueue.applyLocks(vault); // Unlocked: "Encrypt names" can now reach locked entries. AppState.processSettings(); + if (legacy) { + askForNewPin(); + return; + } succeed(); return; } @@ -249,9 +268,23 @@ class PinView extends WatchUi.View { vault.finishSetPin(_job as PinJob); _job = null; vault.promotePending(); + ChangeQueue.applyLocks(vault); succeed(); } + // After a legacy PIN unlocked: the same screen asks for a new 4-digit + // PIN, and onSuccess runs once that is set. BACK leaves it unlocked + // with the old PIN, which asks again at the next unlock. + private function askForNewPin() as Void { + AppState.activity(); + legacy = false; + mode = MODE_CHANGE; + if (_selector == SELECT_OK) { + _selector = 0; + } + showMessage("Set a new " + KeyManager.PIN_DIGITS + "-digit PIN", false); + } + private function succeed() as Void { AppState.activity(); var next = _onSuccess; @@ -307,8 +340,8 @@ class PinView extends WatchUi.View { 2 * Ui.halfWidthAt(dc, y)), Graphics.TEXT_JUSTIFY_CENTER); y += dc.getFontHeight(Graphics.FONT_SMALL) + 4; - // Dots: entered digits filled, at least RECOMMENDED_DIGITS shown. - var slots = _digits.size() > RECOMMENDED_DIGITS ? _digits.size() : RECOMMENDED_DIGITS; + // Dots: entered digits filled, PIN_DIGITS slots (more for a long legacy PIN). + var slots = _digits.size() > KeyManager.PIN_DIGITS ? _digits.size() : KeyManager.PIN_DIGITS; var r = h / 60 > 3 ? h / 60 : 3; var step = r * 3; var x = cx - (slots - 1) * step / 2; @@ -367,6 +400,9 @@ class PinView extends WatchUi.View { _keys = [] as Array<Array<Number>>; var log = ""; for (var i = 0; i < 12; i++) { + if (values[i] == KEY_OK && !legacy) { + continue; // no OK key: the 4th digit submits; the slot stays empty + } var kx = left + (i % 3) * keyW; var ky = top + (i / 3) * keyH; _keys.add([kx, ky, keyW, keyH, values[i] as Number]); @@ -468,11 +504,16 @@ class PinDelegate extends WatchUi.BehaviorDelegate { return true; } + // START on a touch screen submits only a legacy PIN. Otherwise it does + // nothing: one tap can arrive both here and in onTap, and submitting + // the digits typed so far was a wrong PIN. function onKey(event as WatchUi.KeyEvent) as Boolean { var v = view(); if (v != null && Ui.isTouch() && event.getKey() == WatchUi.KEY_ENTER) { - v.markChanged(); - v.press(PinView.KEY_OK); + if (v.legacy) { + v.markChanged(); + v.press(PinView.KEY_OK); + } return true; } return false; |
