Skip to content

Repository files navigation

fpmatch

CILicence: Apache-2.0 OR LGPL-2.1+Language: C99Donate

A self-contained correlation-based fingerprint matcher for small-area press sensors, written in plain C99 with no dependencies beyond libm.

./install.sh # build, self-check, install into ~/.local
fpmatch pair A.pgm B.pgm # one similarity score on stdout

It exists because on sensors this small the open-source stack does not merely match badly — it declines to match at all. libfprint's only matcher for an image device is NBIS bozorth3, which hard-returns a zero score unless both prints yield at least MIN_COMPUTABLE_BOZORTH_MINUTIAE = 10 minutiae (libfprint/nbis/include/bozorth.h:122). A 144x64 press capture yields 1 to 10. On the pool measured here every one of 2556 dev and 3144 test pairs scored exactly 0.0 — EER 50%, indistinguishable from a coin. fpmatch replaces that with band-limited phase-only correlation, which does not care how many minutiae there are, and ships a patch that lets libfprint use it (integration/libfprint/).

Developed against the ELAN 04f3:0c3d pad: 144x64 px, ~7x3 mm, ~500 dpi, 8-bit.

Three pieces, one problem

#piecewhere
1Press capture, fixed. The ELAN driver was running swipe frame-assembly on a static press, stitching a seamed 216x121 composite out of a 144x64 sensor. The fix takes the press as the press.a libfprint fork (libfprint-elan-press), not in this repository
2fpmatch. Band-limited phase-only correlation. C99, zero dependencies, ~2900 lines of src/ including comments, plain cc and make.src/, and this whole repo
3Coverage-guided enrolment. Wires libfprint's existing fpi_image_device_retry_scan() to a real coverage metric, so enrolment asks for the parts of the finger it does not have yet.integration/libfprint/COVERAGE.md

Does it actually work?

Two answers, and they disagree. Both are below, because publishing only the flattering one would be the dishonest move.

Answer 1: offline, held-out benchmark

Held-out test split, TAR at FAR = 0, enrolment = scans 01-14, probes = scans 25-40, 48 genuine and 96 imposter decisions:

systemtest TAR@FAR=0FAR
NBIS mindtct+bozorth3 — what libfprint ships0.0% (0/48)0/96
fpmatch, --template images (the default)18.8% (9/48)0/96
fpmatch, --template mosaic (option, off by default)25.0% (12/48)0/96
ELAN's own engine at its shipped threshold39.6% (19/48)0/96

Paired McNemar on the same 48 probes: fpmatch beats NBIS p = 0.0039; the vendor beats fpmatch-images p = 0.0129; the vendor vs fpmatch-mosaic is p = 0.0654, not significant. Enrolled EER for the default configuration on the held-out split is 39.58% (~39%).

Read on its own, 18.8% says roughly four presses in five are rejected.

Answer 2: the real GNOME lock screen

The same binary, at the same frozen defaults, driving fprintd and PAM against the physical reader. Numbers reconstructed from journald after the fact, so this is an observation of ordinary use and not a designed experiment.

20 attempts, 12 accepted. The last three attempts (best scores 0.2273, 0.1706, 0.1704) were the author deliberately presenting other fingers to see whether they would get in. Splitting on that:

attemptsacceptedrate
genuine (enrolled finger)171270.6% TAR
imposter (deliberate other fingers)300% FAR

The best imposter scored 0.2273 against a live threshold of 0.2755.

The offline split predicted 16.7% for this configuration. Getting 12 or more accepts out of 17 attempts if the true rate were 16.7% has probability 1.3e-06. Whatever is going on, it is not a lucky run.

The genuine/imposter split is inferred, from the timing in the log plus the author's account of what he was doing. Nobody wrote down "this attempt is finger X" at the time. So here is the conservative reading as well: if you refuse the inference and count all 20 attempts as genuine, that is 12/20 = 60%. Both readings crush the offline prediction by a wide margin.

The threshold has almost no margin

One of those accepts scored 0.2759 against a threshold of 0.2755. That is a margin of 0.0004, about one part in 700 of the score.

The threshold is derived from the dev split of one person's fingers on one ELAN pad. It is per-sensor and per-person, the score scale moves when the matcher's parameters move, and a value that is correct here can be a lockout or a hole somewhere else. Re-derive it for your hardware.docs/CALIBRATION.md is the recipe, and integration/libfprint/DESIGN.md shows how the shipped 0.2755 was produced.

So what should you take from this?

Synthetic cross-session benchmarks substantially understate real-world performance on this kind of sensor. The offline protocol pairs an enrolment from one sitting against probes from a different sitting weeks later, chosen without regard to how the finger was placed. A person at a lock screen does something else entirely: they place the finger the way they always place it, they can see the sensor, and if it does not open they simply press again. That gap is worth more than 50 points here.

What this does not mean:

  • It is not "reliable". It is one person's finger, on one sensor, at a threshold with a 0.0004 margin, over 20 logged attempts.
  • The imposter fingers were the author's own other fingers. That measures finger separation, not a population false-accept rate. No stranger's finger has ever been presented to it.
  • The 0% FAR above rests on three imposter attempts. Three.

If you run it, please report your numbers, good or bad. Use the accuracy report issue template. Ten people reporting real figures would do more for this project than any algorithm change, and "useless on my hardware" is as valuable as a good result.

The offline numbers are not an accuracy claim either

  • One subject. Three fingers. 120 captures. Genuine pairs are the same finger; imposter pairs are a different finger of the same person.
  • Therefore "FAR = 0" is a finger-separation rate, not a population false accept rate.
  • The finest FAR this pool can resolve is 1/96 = 1.04%. "FAR = 0" means "no false accepts in 96 decisions", 95% CI [0, 3.8%] — it does not mean "below 0.1%", and the harness's TAR@FAR=0.1% column is meaningless here.
  • No EER in this repository is a certified accuracy figure. There is no standardised corpus behind them (no FVC, no NIST SD), no third-party scoring, and no vitality/spoof testing at all.
  • The offline comparison is biased towards fpmatch — its threshold is chosen on the same data it is then scored on, while the vendor's is fixed in a DLL — and fpmatch still loses there.

Full measurement, protocol, significance tests and caveats: eval/RESULTS.md. The engineering story, including what to try next: FINDINGS.md.


Why I built this

This section is Dragos writing in the first person. Everything else in this repository is measurement; this part is just how it happened.

It started with a password manager. I wanted my iCloud Keychain available in Zen browser on Linux, which meant writing a little helper to hold the credentials, and I thought it would be nice if my fingerprint reader could unlock it. The reader had always worked in Windows. On Linux it was a coin flip.

Enrolment would complete happily and then verification would refuse me. I spent a while assuming I had set something up wrong. The moment I stopped being able to let it go was when I enrolled my index finger, put my middle finger on the pad, and watched sudo let me straight through. A reader that rejects the right finger is annoying. A reader that accepts the wrong one is a different thing, and I wanted to know which of the two problems I actually had.

Answering that took a long detour. I built an offline Windows 11 VM purely so I could watch how Windows drives the same piece of hardware, on the theory that if Windows could do it, the silicon could do it. What I found was that the sensor does no matching at all. The matching is a software DLL that ships with the driver. That was good news twice over: it meant Linux was not missing some magic chip, and it meant I could run the vendor's matcher under Wine and use it as a measuring stick for anything I wrote. Having a number to beat changed the whole project.

Then came the part nobody writes about. For a long stretch my imposter scores were higher than my genuine ones, which is worse than useless, and I could not see why. I ran a sweep of 133 configurations across every parameter the matcher has, and it found nothing that survived its own noise. One tuning run ate 69 GB of RAM and took my desktop down with it. There were evenings where the honest summary of the day's work was a negative result, and I wrote those down too, because a repository full of only the wins would be a repository I could not trust six months later.

It unlocks my GNOME lock screen now. I still would not call it solved, and the numbers above say exactly how far from solved it is. But my reader works, it works for the right finger, and the whole thing is under three thousand lines of C that builds in under a second. That will do for now.


Quick start

./install.sh # build + self-check + install into ~/.local
./install.sh /usr/local # or a system prefix you can write to
./install.sh --uninstall # put it back

or by hand, which is the same thing:

make # cc + make, links only -lm
make check # self-test: no data, no framework, no network
sudo make install PREFIX=/usr/local

There is no configure step, nothing to download and no optional dependency. A cold single-threaded build of the whole matcher takes about 0.6 s and produces a 53 KB binary from ~2900 lines of C.

What is in here

pathwhat
src/the matcher: FFT, BLPOC, PGM I/O, template format, mosaic enrolment
tests/make check — assert-based self-test plus a pair==batch consistency check
install.shone-command build, self-check and install (or uninstall)
reproduce.shregenerate every published number from a clean build
eval/the scorer-agnostic evaluation harness, the sweep driver, significance tests
eval/RESULTS.mdevery measured number, with the command that produced it
FINDINGS.mdwhat was learned, where the wall is, what to try next
integration/libfprint/the patches, design notes and end-to-end tests that make libfprint use fpmatch
docs/CALIBRATION.mdcalibrating parameters and a threshold for your sensor
docs/YOUR-OWN-CAPTURES.mdhow to build your own capture pool (there is none in this repo)
docs/MOSAIC-DESIGN.mdthe mosaic design note, as written before the result was known
LICENSING.mdlicence, clean-room statement, template-privacy trade-off
CONTRIBUTING.mdwhat helps most, and the one rule with no exceptions
tools/devsep.pyquick genuine/imposter separation summary during development

There are no fingerprint images in this repository and there never will be. A fingerprint is biometric data about a specific living person and cannot be rotated after a leak. .gitignore excludes *.pgm and CI fails a push that adds one. To reproduce the accuracy numbers you need your own captures — see docs/YOUR-OWN-CAPTURES.md.

Why correlation and not minutiae

The usual open-source path (NBIS mindtct + bozorth3) extracts minutiae — ridge endings and bifurcations — and matches their relative geometry. That needs enough of them, and a ~7x3 mm window does not contain enough. The failure is not graceful: bozorth3 refuses to score below ten minutiae, so the result is a constant 0.0 rather than a weak score.

Correlation-based matching is the established alternative for small sensors: instead of extracting features it compares the images directly, in the frequency domain, so minutia count never enters into it. The technique implemented here is band-limited phase-only correlation (BLPOC), after Ito, Nakajima, Kobayashi, Aoki and Higuchi, "A fingerprint matching algorithm using phase-only correlation", IEICE Trans. Fundamentals E87-A(3), 2004. It is implemented from the published description of the method; nothing here is derived from any vendor binary (see LICENSING.md).

How it works

  1. Both images are preprocessed: sensor fixed-pattern (column) noise removed, local mean/variance normalisation to flatten uneven contact pressure, then global normalisation. Optionally a 2D Hanning window.
  2. Each is zero-padded into a power-of-two canvas (256x128 by default) and forward-transformed with a vendored radix-2 FFT.
  3. The cross-phase spectrum R = A·conj(B) / |A·conj(B)|^alpha is formed. With alpha = 1 this is classical phase-only correlation: every frequency is whitened to unit magnitude. Lower alpha keeps some of the amplitude.
  4. Band limiting: only the low/mid frequency rectangle |u| <= K1, |v| <= K2 is kept, as fractions of Nyquist (--k1, --k2). The DC bin and, by default, the u = 0 / v = 0 axes are dropped — they carry the sensor's stripe and banding artefacts, not ridge structure.
  5. An inverse FFT gives the correlation surface, normalised so two identical images peak at exactly 1.0 for any alpha.
  6. The score is the highest peak inside a plausible-translation window; the peak's position is the estimated translation, reported by --verbose.
  7. All of that is repeated over a coarse rotation search (default ±20° in 4° steps) and the best score wins. Placement angle varies a lot on a pad this small; without the search, genuine pairs are missed.

--score psr switches from raw peak height to peak-to-sidelobe ratio. It is much worse here (see the sweep) and is kept only because it is the textbook alternative.

Commands

fpmatch pair A.pgm B.pgm one float on stdout, higher = closer
fpmatch enroll OUT.tmpl A.pgm B.pgm ...
fpmatch verify TMPL.tmpl PROBE.pgm one float, max over the enrolled set
fpmatch batch < pairs.txt 'A.pgm B.pgm' per line -> score per line
fpmatch mosaic PREFIX A.pgm B.pgm ... write the mosaic fragments as PGMs
fpmatch --help

pair and verify print exactly one float and nothing else. Failures go to stderr with a non-zero exit status — a failure is never reported as a score of zero. batch exists so an evaluation can score thousands of pairs without paying process startup and FFT setup per pair; it caches prepared spectra (--cache N).

A default template is just the enrolled images, and verification is the max pair score over the set. That keeps enrolment lossless, at the cost of template size.

Mosaic enrolment (--template mosaic, not the default)

The pad is ~7x3 mm, so each press sees a small arbitrary patch and most same-finger pairs share almost no area. Enrolling more images only buys more lottery tickets. Mosaic enrolment instead registers the enrolled captures to each other — BLPOC already returns the translation, not just a score — and composites them onto larger canvases:

fpmatch --template mosaic enroll gallery.tmpl scan-*.pgm
fpmatch verify gallery.tmpl probe.pgm # the template records its mode
  1. Every pair among the enrolled captures is scored, keeping the peak and the recovered (dx, dy, angle).
  2. Edges above --reg-thresh form a graph; fragments grow outward from the strongest edge, largest first (a maximum spanning forest).
  3. Loop closure is checked. Before a capture is admitted, its implied placement must agree, within --loop-tol, with every other confident edge back into the fragment — not just the one it arrived on. Registration errors otherwise propagate silently down a spanning tree.
  4. Overlaps are blended by local ridge contrast rather than a plain mean, and frame borders are feathered (--taper), so a smeared press does not wash out a sharp one and butted captures leave no hard seam.
  5. Captures that never register confidently stay as separate fragments. With nothing registered, every capture is its own fragment and the result is bit-identical to --template images — pinned by make check and by a --reg-thresh 1.1 control run on real data.

Verification locates the probe on each canvas, crops the canvas to that footprint and scores the crop with the ordinary matcher, so scores stay on exactly the fpmatch pair scale and thresholds carry over.

It is not the default, and the reason is the honest one. It raised held-out TAR@FAR=0 from 18.8% to 25.0%, three probes gained, none lost, FAR unchanged — but all three gained probes are thumb, index and middle did not move by a single decision, and mosaicking only works on a finger whose presses overlap enough to register at all. It costs about 2x the verification time and stores a reconstruction of the finger larger than any capture the sensor ever took. Turn it on deliberately for large-contact-area enrolments; do not expect it to fix a finger that will not register. eval/RESULTS.md §10, eval/results/mosaic-DECISION-RULE.md, and LICENSING.md#template-privacy for the privacy trade.

Parameters

Every parameter is a flag with a default, so tuning never needs a recompile. fpmatch --help prints the current defaults.

flagdefaultwhat it does
--k1 F0.7horizontal band limit, fraction of Nyquist
--k2 F0.7vertical band limit, fraction of Nyquist
--hp F0high-pass cut around DC, fraction of Nyquist
--alpha F0.25whitening exponent; 1 = phase-only, 0 = plain cross correlation
--window 0|102D Hanning window before the FFT
--fpn 0|1|21fixed-pattern noise removal: none / column means / column+row
--drop-axes 0|11ignore the u=0 and v=0 spectrum axes
--enhance 0|11local mean/variance ridge normalisation
--enhance-win N11window for --enhance, pixels
--rot-range D20rotation searched, ± degrees
--rot-step D4rotation step in degrees; 0 disables the search
--max-dx N60largest plausible horizontal shift, pixels
--max-dy N28largest plausible vertical shift, pixels
--overlap-norm 0|10divide the peak by the overlapping image fraction
--min-overlap F0.25reject shifts overlapping less than this
--score peak|psrpeakscoring mode
--psr-radius N5peak exclusion radius used by psr
--fft WxH256x128zero-padded transform size, powers of two
--cache N96prepared spectra cached in batch mode
--template images|mosaicimagesenrolment mode (see above)
-voffpeak, PSR, angle and translation on stderr

Tuning is the whole result

Textbook BLPOC settings are chance on this sensor. Measured on the dev split (scans 01-24, 72 images, 828 genuine and 1728 imposter pairs):

changepairwise EERd'
textbook BLPOC (--alpha 1 --window 1 --k1 0.35 --k2 0.35)0.4900.26
turn the Hanning window off0.4690.31
widen the band to 0.70.3830.59
partial whitening --alpha 0.250.3070.86
widen the rotation search to ±20°0.2950.91

Whitening (--alpha) is the single biggest lever. Full phase-only correlation normalises every retained frequency to unit magnitude, which on low-contrast captures promotes pure sensor noise to the weight of the ridge band. Windowing hurt, which is the opposite of the usual advice: on a pad this small two captures of the same finger usually overlap at an edge, and a Hanning window attenuates exactly the evidence.

The defaults are frozen, and the sweep that froze them found nothing

A later stage swept 133 configurations of all 18 parameters over the dev split by coordinate descent. No configuration beat these defaults by more than the pool's own noise. The best improved dev pairwise EER from 0.295 to 0.256, but a paired bootstrap over images puts that at −0.038 with a 95% CI of [−0.089, +0.005], and it accepted four fewer genuine probes at the FAR = 0 operating point. The freeze rule was written down before the sweep finished and before any contact with the test split; the defaults won by default. Everything, including which parameters do literally nothing, is in eval/RESULTS.md §4.

Two properties worth knowing before you tune anything:

  • pair and batch return bit-identical scores for the same parameters, and make check fails the build if they ever diverge (tests/cli-consistency.sh).
  • Matching is not symmetric: only the second image is rotated, so pair A B and pair B A differ slightly. Pick a convention and keep it.

Using this on a different sensor

These defaults are fitted to one ELAN 04f3:0c3d pad, and the shipped 0.2755 threshold is fitted to one person's fingers on it. --alpha, --k1/--k2, --enhance-win and the rotation range all depend on ridge pitch in pixels, sensor noise character and how large a patch a press covers, none of which transfer. Assume you need your own sweep and your own threshold.

The full recipe, with commands you can paste, is docs/CALIBRATION.md. In outline:

  1. Collect a pool: one flat directory of 8-bit binary PGM, label-NN.pgm, several fingers, tens of scans each (docs/YOUR-OWN-CAPTURES.md).

  2. Split it by scan number and never look at the held-out half:

    python3 eval/evaluate.py --pool POOL --scorer "$PWD/fpmatch" \
    --name baseline --split dev --enroll-scans 1-14 --out out/baseline
    
  3. Sweep on dev only, with a selection metric declared in advance:

    python3 eval/sweep.py --pool POOL --jobs 4 --rounds 3 --out sweep.json
    
  4. Bootstrap over images, not pairs, before believing any win:

    python3 eval/sweep.py --pool POOL --mode list \
    --configs candidates.json --bootstrap 400
    
  5. Derive your threshold on dev, then score the held-out split once and report what it says.

If you do this, expect to find what this project found: the dominant limit on a sensor this small is capture overlap, not the matcher. See FINDINGS.md.

What it costs to run

Measured single-threaded on one core of an AMD Ryzen 7 9800X3D (/usr/bin/time -f "%e %M"), 144x64 captures, frozen defaults:

operationwallpeak RSS
fpmatch pair A B (11 rotations, 256x128)21 ms4.1 MB
verify against a 14-image gallery, --template images0.28 s4.1 MB
verify against a 14-image gallery, --template mosaic0.53 s10.3 MB
enroll, --template images (a copy)<10 ms2.3 MB
enroll, --template mosaic (91 pairwise matches)1.8 s4.4 MB
batch, 100 pairs, default --cache 961.6 s53 MB
make check (whole self-test)2.4 s38 MB
cold single-threaded build0.6 s

Template sizes for a 14-image thumb gallery: 129 KB images, 98 KB mosaic. A full dev evaluation — 2556 pairs — takes 12.9 s at -j 4.

The only memory knob that matters is batch's spectrum cache: it holds --cache N prepared spectra, so a large N on a large FFT size is the one way to make this process big. FPMATCH_CACHE_MB caps it in megabytes. Nothing here needs a GPU, a BLAS, or more than a laptop.

Reproducing the published numbers

./reproduce.sh # POOL from $POOL or the default path
POOL=/path/to/captures ./reproduce.sh

It cleans, rebuilds, runs the self-checks, runs the harness's own synthetic self-test, and then — only if a capture pool is present — regenerates the evaluations, the comparison plots and the libfprint end-to-end test. With no pool it stops after the data-free stages and says so rather than inventing numbers.

Using it from libfprint

integration/libfprint/ carries a patch against libfprint 1.94.10 that adds fpmatch as an opt-in, per-driver matcher: the print stays FPI_PRINT_NBIS, the raw images ride along in the FP3 format's existing expansion slot, and a driver opts in with one call. A libfprint without fpmatch reads such a print fine and falls back to bozorth3, and prints enrolled before the change keep working.

It is exercised end to end through the virtual_image driver with no hardware (e2e-virtual.sh, 5/5 pass), where the standalone CLI is the oracle and libfprint's score must agree with it to four decimal places. Beyond that it has been run live against the physical reader through fprintd and PAM, which is where the lock-screen numbers at the top of this file come from. That was ordinary use rather than a controlled test, and it is the only hardware any of this has ever touched.

Read integration/libfprint/DESIGN.md, especially the threshold derivation and the "what is NOT verified" list, before doing anything with it.

Coverage-guided enrolment

The third piece, and the one with the most interesting negative result. libfprint's enrolment is a fixed number of stages with a per-stage pass/fail, so a driver cannot say "that press was fine but I still have nothing from the left side of your finger". ELAN's Windows driver does exactly that, and it is the single largest difference between the two stacks. The patch in integration/libfprint/COVERAGE.md wires the existing fpi_image_device_retry_scan() primitive to a real coverage metric so the enrolment can ask for the placements it is missing.

It works mechanically, and it did not improve matching: on the sealed split a guided gallery accepted fewer genuine probes than a count-matched freeform one (3/48 vs 7/48, p = 0.2891, not significant), and session-to-session variance turned out to be larger than the effect being measured. The whole measurement is in eval/GUIDED-RESULTS.md, pre-registered in eval/GUIDED-DECISION-RULE.md before the split was read.

Privacy

A correlation matcher stores image-like data. The default template is the enrolled captures themselves; a mosaic template is worse, because it is a single reconstruction of the finger larger than anything the sensor captured, and fpmatch mosaic will render it as a PGM for anyone holding the file. That is a real regression against minutiae templates and it is not solved here, only documented: LICENSING.md#template-privacy.

Roadmap

No dates. This is one person's evenings, and the order will be driven by what people report.

itemstate
Other sensorsCompletely untested. Every number here is one ELAN 04f3:0c3d. If fpmatch is useless on your pad, that is a finding and it should be filed.
More than one subjectThe single largest weakness. One person and three fingers is what stands behind every statistic in this repository, and cross-finger imposters are finger separation rather than population FAR.
Per-sensor threshold derivationToday 0.2755 is a constant with a comment. It should be something a user derives on their own data with a documented command, and eventually something the driver can carry per device.
libfprint upstreamingIntended, and the reason the LGPL option is retained. The open question upstream will ask is template privacy, since this stores image-like data in /var/lib/fprint.
Capture, not matchingFINDINGS.md §8 argues the next real win is on the capture side. The matcher has been swept to exhaustion; the sensor has not.
Everything elseDriven by the accuracy reports that come in.

The full research corpus

Everything measured, in the order a cold visitor would want it.

documentwhat it answers
FINDINGS.mdThe engineering story. Why NBIS refuses to score, why textbook BLPOC is chance, why capture overlap is the real wall, and what to try next. Start here.
eval/RESULTS.mdEvery number with the command that produced it: the sweep, the freeze, the single test run, the vendor comparison, the NBIS baseline, the caveats.
eval/GUIDED-RESULTS.mdDid coverage-guided enrolment help? Measured on the sealed split. No.
eval/GUIDED-DECISION-RULE.mdThe pre-registration for that experiment, written before the split was read.
eval/results/mosaic-DECISION-RULE.mdThe same discipline for the mosaic stage, including the postscript admitting the rule was too weak.
integration/libfprint/DESIGN.mdHow fpmatch plugs into libfprint, what was rejected and why, how 0.2755 was derived, and what is not verified.
integration/libfprint/COVERAGE.mdThe coverage metric, its policy thresholds, and what libfprint's enrolment API cannot express.
integration/libfprint/CAPTURE-PROTOCOL.mdWhat the human is actually asked to do during a guided capture session.
docs/MOSAIC-DESIGN.mdThe mosaic design note as written before the result was known, success criterion included.
docs/CALIBRATION.mdCalibrating parameters and a threshold for a sensor that is not this one.
docs/YOUR-OWN-CAPTURES.mdBuilding a capture pool, since this repository ships none.
LICENSING.mdLicence split, clean-room statement, provenance audit, template privacy.

Contributing

See CONTRIBUTING.md. The short version: never commit, attach or upload fingerprint images, templates or print stores, not even your own, because a fingerprint cannot be rotated after a leak. Scores, counts, thresholds and log lines are all welcome. Pixels are not.

The most useful contribution is almost certainly not code. It is your numbers, from your sensor, measured properly.

Support

fpmatch is free and always will be. If it got your reader working, or if the write-up saved you a weekend, you can say thanks here:

Donate

There is no paid tier, no sponsor-only feature and nothing withheld. Reporting your accuracy numbers is worth more to the project than money.

Contact

  • Bugs, sensor reports and accuracy numbers belong in issues, where the next person with the same hardware can find them.
  • Collaboration and commercial enquiries, or getting this working on hardware you cannot share publicly: email Dragos at dragosrobu@icloud.com.

Licence

Dual-licensed Apache-2.0 OR LGPL-2.1-or-later, your choice, with one exception.

whatlicence
the matcher: src/, tests/, eval/, tools/, docs/, build filesApache-2.0 OR LGPL-2.1-or-later
integration/libfprint/LGPL-2.1-or-later only

Apache-2.0 is there so anyone can use this, including in a closed product, and it carries a patent grant that matters in a field as encumbered as biometrics. LGPL-2.1-or-later is retained because libfprint is LGPL-2.1-or-later and could not accept an Apache-only contribution, and upstreaming is a goal.

integration/libfprint/ is a derivative work of libfprint and legally cannot be offered under Apache-2.0, so it stays LGPL-2.1-or-later. If you vendor fpmatch under Apache-2.0, take src/ and leave integration/ behind.

Full licence texts: LICENSE-APACHE and COPYING, both byte-identical to their canonical upstream versions and checksum-pinned in CI. The clean-room statement and the provenance audit are in LICENSING.md.

About

Correlation-based fingerprint matcher for small-area press sensors, where libfprint's bozorth3 refuses to score at all. Plain C99, no dependencies, plus a libfprint patch. Every number measured and caveated.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages