Pick the Alphabet to Suit the Reader: Damm64 and Damm93 Joins Damm32

Close-up of a grey keyboard showing neighbouring keys such as T, Y, U, G, H and J

My damm32 library puts a Damm check digit on base32 codes, and it now has two new siblings: damm64 for the base64url alphabet and damm93 for the 93 visible ASCII characters other than backslash. Each has a matching C, PHP and JavaScript implementations. The obvious question is now which one you should use. The answer depends on who (or what) has to read the code.

A Quick Background of the Damm Algorithm

The Damm algorithm adds one character to the end of a code string that can check for mistyped letters. It detects every single-character error and every adjacent transposition (two neighbouring characters swapped), the two most common transcription errors. Importantly, leading zeros doesn’t change the check digit. Nearly all implementations have been base 10 only, useful for number checking, but not so useful for longer strings.

Which Version of the Damm Algorithm Should You Use?

damm32 damm64 damm93
Alphabet Lowercase letters and digits without look-alikes, plus * base64url Visible ASCII except \
Bits per character 5 6 about 6.5
Case Insensitive Sensitive Sensitive
Best for Codes people read and type IDs and tokens that are copied, or used in URLs Compact machine-to-machine strings

The simple rule is if a human types it then use damm32. If a human copies and pastes it then use damm64. If only machines touch it then use damm93. Each step to the right packs more information into every character, and makes each step harder for a person to read aloud or retype. The upside of using the larger versions is the same information can be packed into a shorter string.

damm64

damm64 uses the base64url alphabet from RFC 4648 section 5, so base64 zero is A and the codes are safe to drop into a URL.

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_

Input is case-sensitive. Here it is appending a check digit and then catching mistakes.

$ damm64 -a Hello
Hellop

$ damm64 -c Hellop && echo valid
valid

$ damm64 -c Hellpo || echo invalid
invalid

$ damm64 -c hellop || echo invalid
invalid

The last two are a transposition and a wrong-case letter, and both are caught. The catch is that base64 is full of easily confused characters (0 and O, l and I), so people will make those errors far more often than they would with damm32. damm64 will detect them, but it is much better to use it where the code is copied and pasted than where someone reads it off a page and types it. Strip any = padding before checking base64 data.

This is the one-liner I would use to generate a random 22 character token (21 random characters plus the check digit).

openssl rand -base64 32 | tr '+/' '-_' | tr -d '=\n' | head -c21 | damm64 -a

damm93

damm93 uses the visible ASCII characters (! to ~) except backslash. The alphabet runs from 0 to ~ (skipping the backslash) and then wraps from ! to /, so base93 zero is 0 and the digits keep their face values. Input is case-sensitive. Space, backslash, control characters (tab, newline, DEL) and anything outside ASCII are invalid.

$ damm93 -a World
Worldq

$ damm93 -a 'x=1;y=2'
x=1;y=2L

$ damm93 -c Wolrdq || echo invalid
invalid

You get about 6.5 bits per character, which makes for the shortest strings of the three, but there are some traps.

  • The check digit is always visible. Space is not in the alphabet, so trimming whitespace can never remove a check digit. The price is that codes can’t contain spaces.
  • There is no backslash either. Backslash is the escape character in JSON, C, regular expressions and most shells, so leaving it out removes the worst quoting problems. The check digit can still be a quote, $, & or similar (damm93 -a Hello gives Hello"), so quote codes on the command line and escape them in SQL and shell scripts.
  • Damm only guarantees single-character errors and adjacent swaps. That is ideal for human typing errors, but for data corrupted in transit a CRC detects far more kinds of damage.

How They Work Without a Lookup Table

The algorithm needs a quasigroup (an n by n table where every symbol appears once in every row and column), and neither table is stored. Both are computed from a single formula instead.

damm64:  x * y = 2 . (x XOR y) in GF(64), primitive polynomial x^6 + x + 1
damm93:  x * y = 2 . (x - y) mod 93

The XOR (or subtraction) puts zeros on the diagonal, which is why a correct code ends in the zero symbol (A for damm64, 0 for damm93). The anti-symmetry that catches every adjacent transposition holds because the multipliers involved are non-zero in the field (damm64) or invertible modulo an odd number (damm93). The subtraction formula only works for an odd base, which is why damm93 drops two characters rather than one. The 94 visible ASCII characters would make an even base, and the formula fails. After the bug in my original damm32 alphabet (my fault), I am glad to say the tests check every property exhaustively, all 643 and 933 combinations, in all three languages.

Where You Might Use Each Version

Tokens and IDs in URLs (damm64). Invite links, download links and record IDs are copied from emails and chat windows, and a dropped or mangled character is a classic failure. Check the ID at the edge, before it reaches the database, and you can tell the user the link is damaged rather than that the record doesn’t exist.

IDs pasted between systems (damm64). The same applies to references that people carry from one application to another by copy and paste, where the damage is a lost or altered character rather than a typo.

Internal machine-to-machine strings (damm93). Where no person ever reads the string aloud and the field accepts any visible character, damm93 gives the shortest code for a given amount of randomness. Because there is no space to trim and no backslash to escape, it also survives being passed through JSON and configuration files better than a full printable ASCII alphabet would. Log references and long serial numbers are the sort of thing I mean.

Scripts and pipelines (both). With -c the tool prints nothing and sets the exit code, and with no arguments it reads codes from standard input, one per line. Put it at the front of a long job and bad IDs are rejected before any real work starts.

Where You Should Not Use Them

A check digit is not security. It catches honest mistakes, not people. Anyone can calculate it, so don’t use it to stop someone guessing or forging a token – use a signature or MAC for that, and keep the check digit for the typos. And as above, if the main risk is data corrupted in transit rather than mistakes by a human, use a CRC.

Installing Them

Each one installs in a single line (C builds with CMake 3.16 or later, PHP needs 7.4 or later, and JavaScript needs Node.js 20 or later or any modern browser).

composer require danieltillett/damm64
npm install damm64

composer require danieltillett/damm93
npm install damm93

The library calls are the same shape in all three languages. This is JavaScript.

import { checkDigit, isValid } from 'damm64';

checkDigit('Hello');  // 'p'
isValid('Hellop');    // true

The test vectors for each hold every one- and two-character code plus random longer and invalid codes, generated by an independent Python reference implementation, and the C, PHP and JavaScript suites all check against them. CI runs them on Linux, macOS and Windows (C), PHP 7.4 to 8.4 and Node.js 20 to 24. Both are BSD 2-Clause licensed. If you find a code where the three languages disagree, please open an issue on damm64 or damm93 – the tests say it can’t happen, but I have been wrong before.