tothepenny
In your browserDoes it add up?
Bank statement PDFs to a spreadsheet, checked against the balances the bank printed.
Paul Wood FRSA
I build legal technology, at Fifty Six Law and independently, alongside open-source tools and research into characters that pass for one another. Everything here is free to use and built in public.
Does it add up?
Bank statement PDFs to a spreadsheet, checked against the balances the bank printed.
Which characters pass for one another?
Casts rays through glyph outlines to measure how alike two characters look, font by font.
Is this name really free?
One call checks users, organisations, reserved routes and lookalikes before a name is claimed.
Notes from the work: legal technology, the tools above, and the Unicode research behind them.
No pixels. No neural networks. 250,000 confusable pairs across 245 fonts in 86 minutes.
RaySpace is a geometric approach to confusable character detection that compares font vector outlines directly using raycasting. Five layers of signal (intersection counts, positions, crossing angles, ping distances, and ping depth) produce a per-font similarity score without rendering a single pixel.
Read featured post
RaySpace measured glyphs without their size or baseline, averaged only where a pair was found, and used weights nobody had checked against known answers. Testing it against the ASCII letters and TR39's own mappings exposed all three. Release 2 fixes them, adds comparisons across fonts, and ships as a versioned dataset, with corrections to earlier posts and a proposal to Unicode.
tothepenny turns PDF bank statements into a spreadsheet and checks every statement against the bank's own opening, closing, running and day-end balances. Statements that don't reconcile are reported with the reason. It runs locally or in the browser, with PDFium compiled to WebAssembly and Python running in Pyodide, so nothing is uploaded.
RaySpace found 250,000 unique confusable character pairs. Filtering by IDNA2008, IdentifierType, single-script enforcement, and ICANN registry variant tables reduces that to 3,039 cross-script pairs between Recommended characters, and 102 at high confidence. Here's how the filtering works and where the gaps are.
RaySpace draws on a century of ray-based geometry techniques, from Radon's uniqueness theorem and Carmack's raycasting to ambient occlusion and acoustic impulse responses. This post maps the full landscape of prior work in confusable detection and shape analysis, and identifies where RaySpace sits in it.
The first systematic, font-specific multi-character confusable dataset. 676 Latin bigrams tested against 133,000 single-character targets across 245 fonts using geometric SDF comparison. 571,753 pairs found, including 'rn' matching 'm' in 95 fonts and 'oy' matching Cyrillic ѹ at distance 0.000.
Different fonts make different characters confusable. Font-specific confusable maps give your app precise coverage with 6-30x less data.
Thai zero looks like Devanagari zero. Georgian S looks like Cyrillic S. Nobody's checking.
I scanned 22,581 characters from 12 scripts against each other across 230 fonts. 248 confusable pairs between non-Latin scripts have no coverage in TR39, ICANN guidelines, or any existing detection system.
Unicode's confusable detection maps 6,247 characters to Latin targets, protecting English readers from homograph attacks. But a Russian user spoofed by Greek characters, or an Arabic user spoofed by Syriac, falls outside that coverage. confusable-vision can generate the missing data.
confusable-vision's scoring pipeline went from 8.5 hours to 3.5 minutes through three targeted optimisations: replacing sharp with pure JS bicubic resize, moving SSIM computation to a WASM kernel in worker threads, and switching PNG decoding from sharp to fast-png. All results identical to the original pipeline.
Confusable detection pipelines normalise characters to a fixed canvas before measuring similarity. This hides natural size differences. We re-rendered 2,203 scored pairs (TR39 baseline + novel discoveries) at their original sizes and found 254 with width or height ratios above 2x.
130+ API calls, 8 attack types, 4 models. Frontier LLMs read through every pixel-identical substitution. But confusable characters fragment into multi-byte BPE tokens, inflating costs 5x. The comprehension attack fails. The billing attack succeeds.
Can pixel-identical Unicode homoglyphs fool LLM contract review? I tested 8 attack types against GPT-5.2, Claude Sonnet 4.6, and others with 130+ API calls. The models read through every substitution. But confusable characters fragment into multi-byte BPE tokens, turning a failed comprehension attack into a 5x billing attack. Call it Denial of Spend.
Unicode confusable characters create attack surfaces in LLM pipelines, but not the one everyone expects. Frontier models read through every substitution. The real threats are filter bypass (58.7% success), billing inflation (5.2x token cost), and downstream system failures. No major tokenizer defends against any of them.
confusable-vision's Milestone 2b scanned 122,862 CJK, Hangul, Cuneiform, and Egyptian Hieroglyph codepoints against Latin a-z/0-9 across 230 macOS system fonts. 28 novel confusable characters found, all simple geometric primitives.
confusable-vision's Milestone 2 scanned 23,317 Unicode characters not in TR39 confusables.txt against Latin a-z/0-9 across 230 macOS system fonts. 793 novel high-risk pairs discovered, from 96 different scripts.
confusable-vision renders every TR39 confusable pair across 230 macOS system fonts and measures visual similarity with SSIM. 96.5% of confusables.txt is not high-risk, but 82 pairs are pixel-identical in at least one font.
Three attack vectors for Unicode identifier spoofing, a survey of twelve detection systems, and a published benchmark corpus for testing your own defences.
A survey of how 12 real-world systems apply Unicode confusable detection. None chain NFKC into the confusable lookup. Your confusable map needs to match your normalization strategy.
The legal profession has adopted AI as a productivity tool when it should be adopting it as an infrastructure layer. Here's what that difference actually means.
Why a deterministic rule engine beats an LLM for legal document generation — and where the AI actually helps.
AI-generated music deserves proper distribution. I built a browser extension that imports Suno tracks to Oncor with auth separation, idempotent imports, and rights attestation. Here's what we learned.
TR39 and NFKC map 31 characters to different targets. If you build a confusable map for use after NFKC normalization, those entries are unreachable dead code. Here's the full list and how to filter them.
Every multi-tenant app has to solve it. I got tired of solving it badly, so I extracted my solution into a library.