summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-25 12:46:42 +0100
committercirakg <cirakg@cirakg.xyz>2026-10-25 12:46:42 +0100
commit096b5a4d17c85fa5f87db3e72364e9c3506d1e64 (patch)
tree9846544fe7d76d849df4a1ee2058f4847f674e7e /tools
parente0384274bbeab999279bdc35bdddbd8a3a451aeb (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 'tools')
-rw-r--r--tools/gen_fixture_wrap.py16
-rw-r--r--tools/lint_project.py2
-rw-r--r--tools/tests/test_gen_fixture_wrap.py8
-rw-r--r--tools/ui_flow.py15
4 files changed, 28 insertions, 13 deletions
diff --git a/tools/gen_fixture_wrap.py b/tools/gen_fixture_wrap.py
index c389fea..6eb207a 100644
--- a/tools/gen_fixture_wrap.py
+++ b/tools/gen_fixture_wrap.py
@@ -1,16 +1,19 @@
#!/usr/bin/env python3
# Author: cirakg & claude ai
-# Summary: Compute the UI-test fixture's PIN wrap (fake PIN 123456) so the watch needn't run PBKDF2 at start-up.
+# Summary: Compute the UI-test fixture's PIN wrap (fake PIN 1234) so the watch needn't run PBKDF2 at start-up.
"""Print the fake PIN wrap that source/app/Fixtures.mc stores in the UI-test build.
The wrap follows docs/SECURITY.md ("PIN wrap, version 1") with fixed, fake
-inputs: PIN 123456, salt 10..1f, IV 20..2f, master key 40..5f, 2000
+inputs: PIN 1234, salt 10..1f, IV 20..2f, master key 40..5f, 2000
PBKDF2 iterations. Deriving the PIN key on the watch in one event trips the
watchdog, and the UI tests need the store ready before the first screen, so
the fixture stores this precomputed value instead. The app's own unlock code
then checks it like any other wrap, which also cross-checks the format.
+The fixture also stores the 4-digit PIN marker (KeyManager.PIN_LENGTH_KEY =
+PIN_DIGITS), so the PIN isn't treated as a legacy 4-to-10-digit one.
-Usage: gen_fixture_wrap.py (prints FIXTURE_WRAP and FIXTURE_MASTER_KEY in hex)
+Usage: gen_fixture_wrap.py (prints FIXTURE_PIN, FIXTURE_WRAP and FIXTURE_MASTER_KEY in hex,
+ and the marker the fixture stores)
"""
from __future__ import annotations
@@ -20,7 +23,9 @@ import hmac
import struct
import subprocess
-PIN = b"123456"
+PIN = b"1234"
+PIN_DIGITS = 4 # source/storage/KeyManager.mc: every new PIN, and the marker value
+PIN_LENGTH_KEY = "wl" # source/storage/KeyManager.mc
ITERATIONS = 2000
SALT = bytes(range(0x10, 0x20))
IV = bytes(range(0x20, 0x30))
@@ -65,8 +70,11 @@ def fixture_wrap() -> bytes:
def main() -> None:
+ assert len(PIN) == PIN_DIGITS
+ print("FIXTURE_PIN =", PIN.decode())
print("FIXTURE_WRAP =", fixture_wrap().hex())
print("FIXTURE_MASTER_KEY =", MASTER_KEY.hex())
+ print(f"marker: {PIN_LENGTH_KEY} = {PIN_DIGITS}")
if __name__ == "__main__":
diff --git a/tools/lint_project.py b/tools/lint_project.py
index e02215b..04d525f 100644
--- a/tools/lint_project.py
+++ b/tools/lint_project.py
@@ -42,7 +42,7 @@ AUTHOR = "cirakg & claude ai"
HEADER_LINES = 15
DIAG_FILE = Path("source/util/Diag.mc")
# Folders that hold local tool state or generated output, never project files.
-SKIP_DIRS = {".git", ".cache", "build", "node_modules", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache"}
+SKIP_DIRS = {".git", ".cache", ".claude", "build", "node_modules", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache"}
# Written by a tool that would drop a hand-added header on the next run.
GENERATED = {Path("docker/docs/package-lock.json")}
diff --git a/tools/tests/test_gen_fixture_wrap.py b/tools/tests/test_gen_fixture_wrap.py
index 1c72e66..c7b11d5 100644
--- a/tools/tests/test_gen_fixture_wrap.py
+++ b/tools/tests/test_gen_fixture_wrap.py
@@ -31,3 +31,11 @@ def test_hkdf_matches_rfc5869_case_3() -> None:
def test_generated_wrap_matches_the_fixture() -> None:
assert gen_fixture_wrap.fixture_wrap().hex() == fixture_constant("FIXTURE_WRAP")
assert gen_fixture_wrap.MASTER_KEY.hex() == fixture_constant("FIXTURE_MASTER_KEY")
+
+
+def test_fixture_pin_has_the_new_pin_length() -> None:
+ assert gen_fixture_wrap.PIN.decode() == fixture_constant("FIXTURE_PIN")
+ assert len(gen_fixture_wrap.PIN) == gen_fixture_wrap.PIN_DIGITS
+ key_manager = (ROOT / "source" / "storage" / "KeyManager.mc").read_text(encoding="utf-8")
+ assert f"static const PIN_DIGITS = {gen_fixture_wrap.PIN_DIGITS};" in key_manager
+ assert f'static const PIN_LENGTH_KEY = "{gen_fixture_wrap.PIN_LENGTH_KEY}";' in key_manager
diff --git a/tools/ui_flow.py b/tools/ui_flow.py
index a17d435..8c08caf 100644
--- a/tools/ui_flow.py
+++ b/tools/ui_flow.py
@@ -14,7 +14,7 @@ the screen. Watches without a button for an action use touch instead (Venu
Sq 2 has no UP/DOWN and no select button).
The app (UI-test build, source/util/TestMode.mc) starts with fake entries,
-the PIN 123456 and a frozen clock, and prints "DIAG shown <screen>" after
+the PIN 1234 and a frozen clock, and prints "DIAG shown <screen>" after
drawing each screen, so every step waits for the screen it expects instead
of sleeping. Menus are native Menu2 views that print nothing; for those the
script waits a fixed time.
@@ -65,10 +65,10 @@ MENU_SETTLE_S = 1.5 # Menu2 has no "shown" line
SETTLE_S = 0.4 # after a "shown" line, before the screenshot
CLICK_PAUSE_S = 0.25
HOLD_S = 1.6 # long enough for a hold (menu) on every device
-FIXTURE_PIN = "123456" # source/app/Fixtures.mc
-WRONG_PIN = "000000"
+FIXTURE_PIN = "1234" # source/app/Fixtures.mc
+WRONG_PIN = "0000"
FAILURES_UNTIL_WAIT = 5 # source/storage/Lockout.mc: the 5th failure starts a delay
-SELECTOR_OK = 10 # source/ui/PinView.mc: selector positions 0-9, then OK
+SELECTOR_DIGITS = 10 # source/ui/PinView.mc: selector positions 0-9 (no OK; the 4th digit submits)
GYM_CARD = "5901234123457" # source/app/Fixtures.mc: EAN-13 with its check digit
MEMBER_QR = ( # source/app/Fixtures.mc: a version 6 QR
"https://example.com/member/fake-0042?name=Alice%20Example&card=0000111122223333&valid=2027-12-31&level=gold"
@@ -298,12 +298,11 @@ class Flow:
value, xy = item.split("=")
x, y = xy.split(",")
centres[int(value)] = (int(x), int(y))
- for digit in pin:
+ for digit in pin: # the 4th digit submits
self.sim.tap(*centres[int(digit)])
- self.sim.tap(*centres[-2]) # OK
return
- for position in [int(d) for d in pin] + [SELECTOR_OK]:
- steps = (position - self.selector) % (SELECTOR_OK + 1)
+ for position in [int(d) for d in pin]:
+ steps = (position - self.selector) % SELECTOR_DIGITS
for _ in range(steps):
self.sim.press("down")
self.selector = position