#!/bin/sh # fptrial.sh -- a labelled verification protocol against fingerprintd. # # fptrial.sh [correct_presses] [wrong_presses] defaults 15 and 0 # # Runs fprintd-verify once per press and tells you which finger to use before # each one. The daemon's transcript records every frame; THIS records the label # and the wall-clock time from "press now" to the client seeing a result, which # is the latency a user feels. Unlabelled runs cannot be turned into a rate. # # HOLD, DO NOT TAP -- and this script said the opposite for its whole life, # which quietly made every rate this project has ever quoted a worst case. # # Jorijn, 2026-09-05: "press and LIFT (quick tap) is wrong, holding the sensor # until it gives the result is a 100% success rate." The logs agree and say why. # Same template, same session, learning off, only the technique differing: # # tapped 4/15 and 3/15 # held 15/15 and 15/15 # # The mechanism is in the frame metrics. A tap is caught while the finger is # still arriving or already leaving, and such a frame matched 0 times in 53; # with the rescan budget at 0 that rejection is terminal and ends the press. A # held finger yields a full-contact frame, and those match. # # It is also what a real user does: nobody taps a phone's fingerprint sensor and # walks away, they rest a finger until it unlocks. Measuring taps was measuring # a case the product does not have. C=${1:-15}; W=${2:-0} OUT=/tmp/fptrial-$(date +%Y%m%d-%H%M%S).log now() { awk '{gsub(/\./,""); print $1 "0000000"}' /proc/uptime; } run() { # $1 = label printf '\n>>> %s -- press and HOLD until it answers ... ' "$1" t0=$(now) res=$(timeout 20 fprintd-verify user 2>&1 | grep -E 'Verify result' | tail -1) t1=$(now) ms=$(( (t1 - t0) / 1000000 )) verdict=$(printf '%s' "$res" | grep -oE 'verify-(match|no-match|unknown-error)' || echo timeout) printf '%s (%d ms)\n' "$verdict" "$ms" printf '%s %s %d\n' "$1" "$verdict" "$ms" >> "$OUT" sleep 1 } echo "labelled trial -> $OUT" i=1; while [ $i -le $C ]; do run "CORRECT $i/$C"; i=$((i+1)); done # The wrong-finger half is skippable, and skipping it is the right default once # the question it answers is settled. Across every run this project has made the # wrong finger has matched ZERO times out of forty-odd taps; the open question # is the FALSE NEGATIVE rate, and each wrong-finger tap spends a press that # could have measured that instead. Pass a count to put the control back. if [ "$W" -gt 0 ]; then echo; echo "===== now switch to a DIFFERENT finger ====="; sleep 3 i=1; while [ $i -le $W ]; do run "WRONG $i/$W"; i=$((i+1)); done fi echo echo "===== summary =====" awk '$1=="CORRECT"{c++; if($3=="verify-match")cm++; ct+=$4} $1=="WRONG" {w++; if($3=="verify-match")wm++; wt+=$4} END{printf " correct finger: %d/%d matched (false negatives %d) mean %d ms\n", cm,c,c-cm,(c?ct/c:0); if (w) printf " wrong finger: %d/%d matched (false ACCEPTS %d) mean %d ms\n", wm,w,wm,wt/w; else printf " wrong finger: not run this trial\n"}' "$OUT" echo " per-tap log: $OUT"