Tell typists apart
Why it matters "Your typing identifies you" sounds like a scare story until you build the matcher yourself. In this lab you will tell 50 made-up typists apart from timing alone, then switch on a simple defence and measure how much it helps. You will finish knowing both how the trick works and what blunts it.
The plan, in everyday terms
Imagine learning to recognise 50 people by their knock. First you listen to each person knock four times and remember their usual rhythm. Then someone knocks and you ask: whose usual rhythm is this closest to? That is the whole method.
Profile: a summary of one person's usual behaviour, here the average of their timings. Building profiles from known examples is called enrolment.
Feature: one number that describes an example. A list of features turns a messy event (a typed phrase) into something code can compare.
The practice data
typing_samples() from the practice world gives you attempts at typing the set phrase keep it private. Each attempt is a dict:
{"typist": "T07",
"dwell": [96, 104, 88, ...], # 15 numbers, one per key, in milliseconds
"flight": [151, 130, 202, ...]} # 14 numbers, one per gap between keys
The typists are invented by a seeded generator, loosely shaped like ordinary typing. So the accuracy you measure says how well you separate these typists. Real studies report a wide range of results depending on the people, keyboards and text; we make no claim about real-world accuracy.
Measuring "how close"
To compare two lists of timings, add up how far apart they are at each position, ignoring whether the gap is positive or negative. Python's abs() drops the sign: abs(-7) is 7. Python's zip(a, b) walks two lists side by side:
for x, y in zip([100, 150], [90, 170]):
print(x, y, abs(x - y)) # 100 90 10, then 150 170 20
So the distance here is 10 + 20 = 30. A smaller distance means "more alike".
The defence: noise
The noise option adds random jitter of about that many milliseconds to every timing, the way a privacy tool might delay key events by small random amounts before the page sees them. Watch what it does to your accuracy, and think about what it would do to the person typing: 150 milliseconds of random lag on each key would feel sluggish.
In other languages
The distance and nearest-profile steps in JavaScript:
const distance = (a, b) => a.reduce((t, x, i) => t + Math.abs(x - b[i]), 0);
function identify(features, profiles) {
let best = null, bestDist = Infinity;
for (const [name, p] of Object.entries(profiles)) {
const d = distance(features, p);
if (d < bestDist) { best = name; bestDist = d; }
}
return best;
}And the distance in Rust:
fn distance(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b).map(|(x, y)| (x - y).abs()).sum()
}What you should see
With no noise, nearly every attempt is matched to the right typist. Moderate noise (50 ms) costs some accuracy, but most attempts are still matched; heavy noise (150 ms) brings accuracy down sharply. That is the honest trade-off: timing noise helps, but enough noise to hide you also makes typing feel worse. The final lesson of this module looks at defences that do not cost you comfort.
Recap
- Enrol: average each person's timings into a profile.
- Identify: pick the profile with the smallest total gap (distance).
- Random timing noise lowers accuracy, but only heavy noise lowers it a lot, and that costs comfort.
- Accuracy on generated typists describes the generator, not real people.
Write five functions. features(sample) returns sample["dwell"] followed by sample["flight"] as one list. distance(a, b) returns the sum of abs(x - y) over the two lists side by side. build_profiles(samples) returns a dict mapping each typist's name to the position-by-position average of their feature lists. identify(sample, profiles) returns the name whose profile has the smallest distance to the sample's features. accuracy(train, test) builds profiles from train and returns the fraction of test samples that identify gets right (a number from 0 to 1). The printing at the bottom is already written.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.