From a44e0963cef782ba4a369b324a04b32a438df5ce Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Thu, 3 Sep 2026 17:44:35 +0200 Subject: [PATCH] fpenrol.sh: no enrolment ever received the position guidance this script claims pos() was eval'ing over the script's positional parameters from inside a function, where $1 is the function's own argument. So `pos 3` printed "3" and `pos 10` printed "100": every run this script has ever driven printed sample numbers where positions should have been. That rewrites an earlier conclusion rather than just fixing a bug. No enrolment on this device had ever been guided, including the one that produced the best template measured so far -- its spread came entirely from the config's overlap band refusing near-duplicates, not from anything this script said. The paired-test attribution drops a contributor, and the 30-sample run becomes directly comparable to it because both were equally unguided. Prompts are therefore opt-in now, behind FPENROL_GUIDE=1. The first run where they actually fired steered the finger to edges, tips and hard rolls, needed 74 presses for 30 accepts, and produced a template that matched 0 of 10 taps. Prompting a user toward positions they never use in a real tap builds a template of positions they never use in a real tap. The default is the instruction that worked: press as you would to unlock, and let the refusals move you. The stage count comes from the daemon's num-enroll-stages property rather than a second hardcoded 20. --- packaging/fpenrol.sh | 122 ++++++++++++++++++++++++++++++------------- 1 file changed, 87 insertions(+), 35 deletions(-) diff --git a/packaging/fpenrol.sh b/packaging/fpenrol.sh index a8739eb..3204d2a 100755 --- a/packaging/fpenrol.sh +++ b/packaging/fpenrol.sh @@ -7,54 +7,106 @@ # roughly one position, and it matches roughly one frame in seven -- against # 47-75% for the template the research harness enrolled. The algorithm was # given ten near-duplicate images of one spot, so it recognises that spot and -# little else. It accepted them because our config set none of stock's -# enrolment-quality keys and the trustlet's own defaults accept anything; -# those keys are now set, so the algorithm enforces the spread too. +# little else. # # This walks a different contact position per accepted sample. It reads # fprintd-enroll's per-stage output and prompts the next position as each # stage passes, so the guidance tracks what the trustlet ACCEPTED rather than # how many times you pressed. +# +# TWO BUGS FIXED 2026-09-03, both of which had silently disabled a feature +# this script claims to provide: +# +# 1. `pos()` was `eval "printf '%s' \"\$$1\""` over the script's positional +# parameters. Inside a FUNCTION, `$1` is the function's own argument, so +# `pos 3` printed "3" and `pos 10` printed `${1}0` = "100". Every run this +# script ever drove -- including the one that produced the 7/10 template -- +# printed sample numbers where positions should have been, so NO enrolment +# has ever actually received position guidance. The spread in those +# templates came from the config's overlap band refusing near-duplicates, +# not from anything this script said. Positions now live in a newline +# list indexed with sed, which has no interaction with $1 at all. +# +# 2. The sample count was hardcoded to 20 while the daemon was configured for +# 30, so the counter froze at 20/20 and the run looked like it had hung or +# been clamped. It had not: all 30 samples were taken. The count now comes +# from the daemon's own num-enroll-stages property, which it reads from the +# same config the trustlet is given. F=${1:-right-middle-finger} + +# Position prompts are OPT-IN: FPENROL_GUIDE=1. The one template that verified +# well (right-middle, 20 samples, 7/10 quick taps) was enrolled with NO prompts +# -- the pos() bug meant none were ever shown -- and the algorithm's own +# overlap band did the spreading. The first run where prompts actually fired +# (right-middle, 30 samples, edges/tips/rolled-hard) needed 74 presses for 30 +# accepts and then matched 0/10. Prompting a user toward positions they never +# use in a real tap builds a template of positions they never use in a real +# tap. Without prompts, the instruction is the one that worked: press the way +# you would to unlock, and let the REFUSED messages move you. +GUIDE=${FPENROL_GUIDE:-0} + +# The daemon is the authority on how many stages there are. +STAGES=$(busctl get-property net.reactivated.Fprint \ + /net/reactivated/Fprint/Device/0 \ + net.reactivated.Fprint.Device num-enroll-stages 2>/dev/null \ + | awk '{print $2}') +case "$STAGES" in + ''|*[!0-9]*) STAGES=20 + echo "warning: could not read num-enroll-stages; assuming $STAGES" ;; +esac + +POSITIONS='centre of the pad +slightly LEFT of centre +slightly RIGHT of centre +higher up, toward the TIP +lower down, toward the JOINT +centre, rolled LEFT +centre, rolled RIGHT +tip, rolled slightly left +joint, rolled slightly right +centre, a little firmer +far LEFT edge of the pad +far RIGHT edge of the pad +very tip of the finger +well down toward the joint +upper left of the pad +upper right of the pad +lower left of the pad +lower right of the pad +centre, rolled hard left +centre, rolled hard right' +NPOS=$(printf '%s\n' "$POSITIONS" | wc -l) + +# More samples than positions is expected at 30. Cycle, and say so, rather +# than printing nothing -- printing nothing is what hid bug 1 for two sessions. +pos() { + n=$(( ($1 - 1) % NPOS + 1 )) + printf '%s' "$(printf '%s\n' "$POSITIONS" | sed -n "${n}p")" + [ "$1" -gt "$NPOS" ] && printf ' (again, press slightly differently)' +} + i=1 -# Twenty positions, stock's sample count. The config now enforces stock's -# coverage and overlap thresholds, so a sample that does not move enough from -# the last one is REFUSED by the algorithm rather than stored -- the guidance -# and the enforcement finally agree. -set -- "centre of the pad" \ - "slightly LEFT of centre" \ - "slightly RIGHT of centre" \ - "higher up, toward the TIP" \ - "lower down, toward the JOINT" \ - "centre, rolled LEFT" \ - "centre, rolled RIGHT" \ - "tip, rolled slightly left" \ - "joint, rolled slightly right" \ - "centre, a little firmer" \ - "far LEFT edge of the pad" \ - "far RIGHT edge of the pad" \ - "very tip of the finger" \ - "well down toward the joint" \ - "upper left of the pad" \ - "upper right of the pad" \ - "lower left of the pad" \ - "lower right of the pad" \ - "centre, rolled hard left" \ - "centre, rolled hard right" -echo "enrolling $F with position guidance -- 20 samples (stock's count)" -echo "press FLAT and firm, hold about a second, then LIFT fully." +if [ "$GUIDE" = 1 ]; then + echo "enrolling $F with position guidance -- $STAGES samples (from the daemon)" + echo "press FLAT and firm, hold about a second, then LIFT fully." + prompt() { echo ">>> sample $1/$STAGES: $(pos $1)"; } +else + echo "enrolling $F -- $STAGES samples (from the daemon), NO position prompts" + echo "press the way you would to UNLOCK the phone. When a sample is REFUSED," + echo "shift a little from where you were; otherwise do not think about it." + prompt() { echo ">>> sample $1/$STAGES"; } +fi echo -# The reader runs in a subshell, so `shift` there cannot advance the caller's -# list. Index into it instead. -pos() { eval "printf '%s' \"\$$1\""; } -echo ">>> sample 1/20: $(pos 1)" +prompt 1 fprintd-enroll -f "$F" user 2>&1 | while IFS= read -r line; do case "$line" in *enroll-stage-passed*) i=$((i+1)) - if [ "$i" -le 20 ]; then + if [ "$i" -le "$STAGES" ]; then + echo " accepted." + prompt "$i" + else echo " accepted." - echo ">>> sample $i/20: $(pos $i)" fi ;; *enroll-retry-scan*) echo " REFUSED -- move further from the last position, or press flatter" ;; *enroll-completed*) echo; echo "COMPLETED." ;;