13

I'd like to use the Damm algorithm to generate check digits for codes with a 32-character alphabet. The algorithm itself is easily applied to any base (except 2 or 6). The difficulty is the necessary look-up table, which must be a totally anti-symmetric quasigroup with a single character (usually 0) down the main diagonal.

The Wikipedia page gives a table for base 10, and the Python implementation gives a table for base-16, but I haven't found a base-32 example. Does anyone have a suitable table for base-32?

cjm
  • 61,471
  • 9
  • 126
  • 175
  • For someone with slightly more energy with me: Damm's dissertation (in German) is [here](http://archiv.ub.uni-marburg.de/diss/z2004/0516/pdf/dhmd.pdf). The relevant definition is that a Latin square is totally anti-symmetric iff [for all x and y, the (x,y) element equals the (y,x) element iff x = y]. Damm gives a construction for prime power n other than 2 (including the case n = 32) by taking the (x,y) element to be a\*x + y, where a is neither 0 nor 1 and \* is multiplication over an n-element Galois field (Beispiel 5.2). – David Eisenstat May 02 '14 at 15:43
  • Yeah, I'd found that, but I don't read German and I'm hoping somebody's already got runable code based on the dissertation. – cjm May 02 '14 at 15:52
  • Ilmari's answer (https://stackoverflow.com/a/23433934/2526040) is correct for the original question (and a highly recommended read if you're implementing Damm to understand the algorithm better), but a set of TA quasigroups for use with DAMM for all supported bases up to 64 has been published at http://www.md-software.de/math/DAMM_Quasigruppen.txt (quite possibly after this question was asked/answered), which would support a more general implementation (the GF groups are obviously limited to the powers of 2). – archie Aug 23 '21 at 01:19

3 Answers3

13

Inspired by David Eisenstat's answer (and the original dissertation by Damm that he cites), here's a simple Python routine to calculate / verify a Damm checksum for any base 2n for 2 ≤ n ≤ 32:

# reduction bitmasks for GF(2^n), 2 <= n <= 32
masks = (3, 3, 3, 5, 3, 3, 27, 3, 9, 5, 9, 27, 33, 3, 43, 9,
         9, 39, 9, 5, 3, 33, 27, 9, 27, 39, 3, 5, 3, 9, 141)

# calculate Damm checksum for base 2^n
def damm2n (digits, n):
    modulus = (1 << n)
    mask = modulus | masks[n - 2]
    checksum = 0
    for digit in digits:
        checksum ^= digit
        checksum <<= 1
        if checksum >= modulus: checksum ^= mask
    return checksum

The routine takes a list (or, more generally, an iterable) of digits, which are assumed to be integers in the range 0 to 2n−1 inclusive, and the number n of bits per digit (assumed to be in the range 2 to 32 inclusive).

The totally asymmetric quasigroup used by this implementation of the Damm algorithm is given by the map (a, b) &mapsto; 2 ⊗ (ab), where ⊕ denotes addition in the finite field GF(2n) (which is simply bitwise XOR), ⊗ denotes multiplication in GF(2n), and 2 denotes the element represented by the bitstring 0...0102 in the usual n-bit representation of GF(2n).

This is equivalent to the map (a, b) &mapsto; (2a) ⊕ b given by Damm in Example 5.2 of his thesis, except that the input digits are b permuted (by multiplying them with 2 in GF(2n)) to ensure that (a, a) &mapsto; 0 for all a. This is equivalent to permuting the columns of the quasigroup operation table so that the diagonal is all zeros, and allows the checksum to be verified simply by appending it to the original input and checking that the new checksum of the extended input is zero.

The GF(2n) multiplication by 2 is implemented using the usual trick of shifting left by one and, if the n-th bit of the result is set, XORing it with a bitmask corresponding to a monic irreducible polynomial of order n. The specific bitmasks used are taken from the Table of Low-Weight Binary Irreducible Polynomials by Gadiel Seroussi (1998). If you (for some reason) need checksums for bases larger than 232, their table goes up to a whopping 210,000. The Seroussi table lists the exponents of the non-zero coefficients of each reduction polynomial, excluding the constant term; the bitmasks in the code above are obtained by discarding the highest exponent (which is always n), summing together 2k for the other exponents k and adding 1. (Thus, for example, the entry "8,4,3,1" for n = 8 yields the mask 24 + 23 + 21 + 1 = 16 + 8 + 2 + 1 = 27.)

In particular, the code above yields, for n = 4, results matching the base-16 Damm checksum implementation by Johannes Spielmann. This is not guaranteed in general, since there are many possible ways to implement a Damm checksum for a given base, but in this case, the quasigroups used by the two implementations happen to match.


Ps. Here's some Python code to print out the look-up table in a format similar to what the Wikipedia article uses. (Thanks to CJM for the initial version.)

alphabet = '0123456789ABCDEFGHJKLMNPQRTUVWXY' # avoids easy-to-confuse characters
bits = 5

# find out which first single-digit input gives which checksum
r = [-1] * 2**bits
for i in range(2**bits): r[damm2n([i], bits)] = i

# print header
print '  |',  ' '.join(alphabet)
print '--+' + '--' * len(alphabet)

# print rest of table    
for i in range(2**bits):
    row = (alphabet[damm2n([r[i], j], bits)] for j in range(2**bits))
    print alphabet[i], '|', ' '.join(row)
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
  • To amplify your main point: the purpose of the checksum algorithm is that it defines codewords such that (i) each k-digit string is a prefix of a k+1-digit codeword and (ii) no neighbor of a codeword is a codeword, where "neighbor" here means a string derived by replacing one digit or swapping two adjacent digits. The structure of the neighborhoods is preserved when an identical permutation is applied to each digit. – David Eisenstat May 02 '14 at 18:35
  • 1
    To make the table-dumping code work in Python 3, you need to add parens around the parameters to each `print`. But that screws up the output in Python 2. – cjm May 07 '14 at 15:47
4

To summarize the discussion below: we would like a table that has zeros on the main diagonal. Niklas and I have the impression that, rather than being an essential part of the algorithm, this property is merely to avoid solving the equation x*y = 0 in y for x given, where * is the quasigroup operation. With zeros on the main diagonal, we have x = y, but without, we can compute y via one lookup in a 32-element table.

The construction that Damm describes is problematic because it has the desired property if and only if a = -1, but, in characteristic 2, we have 1 = -1. The constraint solver Z3 was not helpful.


Damm's dissertation (in German) is here. The relevant definition is that a Latin square is totally anti-symmetric iff [for all x and y, the (x,y) element equals the (y,x) element iff x = y]. Damm gives a construction for prime power n other than 2 (including the case n = 32) by taking the (x,y) element to be a*x + y, where a is neither 0 nor 1 and * is multiplication over an n-element Galois field (Beispiel 5.2).

Below is the instantiation of this method for n = 32.

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31],
 [2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13, 18, 19, 16, 17, 22, 23, 20, 21, 26, 27, 24, 25, 30, 31, 28, 29],
 [4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11, 20, 21, 22, 23, 16, 17, 18, 19, 28, 29, 30, 31, 24, 25, 26, 27],
 [6, 7, 4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11, 8, 9, 22, 23, 20, 21, 18, 19, 16, 17, 30, 31, 28, 29, 26, 27, 24, 25],
 [8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23],
 [10, 11, 8, 9, 14, 15, 12, 13, 2, 3, 0, 1, 6, 7, 4, 5, 26, 27, 24, 25, 30, 31, 28, 29, 18, 19, 16, 17, 22, 23, 20, 21],
 [12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3, 28, 29, 30, 31, 24, 25, 26, 27, 20, 21, 22, 23, 16, 17, 18, 19],
 [14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1, 30, 31, 28, 29, 26, 27, 24, 25, 22, 23, 20, 21, 18, 19, 16, 17],
 [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
 [18, 19, 16, 17, 22, 23, 20, 21, 26, 27, 24, 25, 30, 31, 28, 29, 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13],
 [20, 21, 22, 23, 16, 17, 18, 19, 28, 29, 30, 31, 24, 25, 26, 27, 4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11],
 [22, 23, 20, 21, 18, 19, 16, 17, 30, 31, 28, 29, 26, 27, 24, 25, 6, 7, 4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11, 8, 9],
 [24, 25, 26, 27, 28, 29, 30, 31, 16, 17, 18, 19, 20, 21, 22, 23, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7],
 [26, 27, 24, 25, 30, 31, 28, 29, 18, 19, 16, 17, 22, 23, 20, 21, 10, 11, 8, 9, 14, 15, 12, 13, 2, 3, 0, 1, 6, 7, 4, 5],
 [28, 29, 30, 31, 24, 25, 26, 27, 20, 21, 22, 23, 16, 17, 18, 19, 12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3],
 [30, 31, 28, 29, 26, 27, 24, 25, 22, 23, 20, 21, 18, 19, 16, 17, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1],
 [5, 4, 7, 6, 1, 0, 3, 2, 13, 12, 15, 14, 9, 8, 11, 10, 21, 20, 23, 22, 17, 16, 19, 18, 29, 28, 31, 30, 25, 24, 27, 26],
 [7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24],
 [1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 29, 28, 31, 30],
 [3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12, 19, 18, 17, 16, 23, 22, 21, 20, 27, 26, 25, 24, 31, 30, 29, 28],
 [13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2, 29, 28, 31, 30, 25, 24, 27, 26, 21, 20, 23, 22, 17, 16, 19, 18],
 [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16],
 [9, 8, 11, 10, 13, 12, 15, 14, 1, 0, 3, 2, 5, 4, 7, 6, 25, 24, 27, 26, 29, 28, 31, 30, 17, 16, 19, 18, 21, 20, 23, 22],
 [11, 10, 9, 8, 15, 14, 13, 12, 3, 2, 1, 0, 7, 6, 5, 4, 27, 26, 25, 24, 31, 30, 29, 28, 19, 18, 17, 16, 23, 22, 21, 20],
 [21, 20, 23, 22, 17, 16, 19, 18, 29, 28, 31, 30, 25, 24, 27, 26, 5, 4, 7, 6, 1, 0, 3, 2, 13, 12, 15, 14, 9, 8, 11, 10],
 [23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8],
 [17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 29, 28, 31, 30, 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14],
 [19, 18, 17, 16, 23, 22, 21, 20, 27, 26, 25, 24, 31, 30, 29, 28, 3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12],
 [29, 28, 31, 30, 25, 24, 27, 26, 21, 20, 23, 22, 17, 16, 19, 18, 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2],
 [31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
 [25, 24, 27, 26, 29, 28, 31, 30, 17, 16, 19, 18, 21, 20, 23, 22, 9, 8, 11, 10, 13, 12, 15, 14, 1, 0, 3, 2, 5, 4, 7, 6],
 [27, 26, 25, 24, 31, 30, 29, 28, 19, 18, 17, 16, 23, 22, 21, 20, 11, 10, 9, 8, 15, 14, 13, 12, 3, 2, 1, 0, 7, 6, 5, 4]]

Below is the extremely crappy Haskell code that made this. It might work for other powers of two. The argument 5 in main is for 2^5.

module Main where
import Data.Char
import Data.List
xs +. ys = simplify (xs ++ ys)
xs *. ys
  = simplify $
      do x <- xs
         y <- ys
         return (x + y)
simplify xs
  = (reverse . map head . filter (odd . length) . group . sort) xs
subseqs [] = [[]]
subseqs (x : xs) = let xss = subseqs xs in xss ++ map (x :) xss
polys n = subseqs [n, n - 1 .. 0]
reduce [] ys = ys
reduce xs [] = []
reduce xs@(x : _) ys@(y : _)
  = if x > y then ys else reduce xs (map ((y - x) +) xs +. ys)
irred [] = False
irred ys@(y : _)
  = let xss = polys (y `div` 2) \\ [[0]] in
      (not . any null . map (flip reduce ys)) xss
irreds n = filter irred (polys n)
ip n = (head . filter irred . map (n :)) (polys (n - 1))
eval xs = (sum . map (2 ^)) xs
timesTable n
  = let ms = ip n
        zs = polys (n - 1) !! 2
      in
      do xs <- polys (n - 1)
         return $
           do ys <- polys (n - 1)
              return (reduce ms ((zs *. xs) +. ys))
verify t
  = all ((1 ==) . length . filter id) $
      zipWith (zipWith (==)) t (transpose t)
main = print $ map (map eval) $ timesTable 5
David Eisenstat
  • 64,237
  • 7
  • 60
  • 120
  • The problem is that to use the quasigroup in the check digit algorithm, you want the main diagonal to be a constant (`f(n,n) = 0` for any *n*). It may be possible to rearrange the table to achieve that. – cjm May 02 '14 at 16:12
  • @cjm I'll look at Damm's thesis some more, but Wikipedia claims that having zeros on the main diagonal is merely a simplification, so that, after computing the product of the digits, one doesn't need to divide the `0` element by this result (since that's the identity operation). – David Eisenstat May 02 '14 at 16:18
  • @cjm What makes this tricky is that in fields of characteristic 2 (e.g., GF(32)), we have -1 = 1; otherwise we could take a = -1. – David Eisenstat May 02 '14 at 16:23
  • I'm trying to figure out how the base-10 table was derived from the one on page 111 of the dissertation. If you rearrange the columns so that the zeros lie on the diagonal, that gives you the first row, but I can't find the algorithm by which the other entries were changed. – cjm May 02 '14 at 16:37
  • @cjm FWIW, the thesis does not say anything about zeroes on the diagonals, so maye it's not the right place to look. According to Wikipedia the table there was derived from the table in the thesis by permutating the columns and then "changing the entries accordingly", whatever that might mean. – Niklas B. May 02 '14 at 16:45
  • @cjm There's no general algorithm. A 4x4 table exists, but not one with 0s on the main diagonal. – David Eisenstat May 02 '14 at 16:51
  • (which is unfortunate because there's an 8x8 with 0s and a direct product construction). – David Eisenstat May 02 '14 at 16:53
  • Actually, if the diagonal is not 0, then I think the best way to verify the check digit would be to strip it off, generate a new check digit, and see if the digit you generated matches the one you stripped. With the 0 diagonal, you simply generate a check digit for the number including the existing check digit and verify that you generated 0. So it's not that big a deal. – cjm May 02 '14 at 17:14
  • @cjm I had the same thought, but I'm concerned that the proof of correctness may not apply to transpositions involving the check digit when the check digit is computed that way (as opposed to solving the equation that makes the product of the whole thing 0). – David Eisenstat May 02 '14 at 17:18
3

If you have a TA quasigroup you can simply rearrange the columns in such a way that the 0 are on the main diagonal. Then the quasigroup is (in general) a WTA quasigroup and can be used for the Damm algorithm. I've done this for order 32, see result below, and this is possible for every order except 2 and 6.

I think the quasigroup of order 10, which can be found in Wikipedia, is constructed by Lemma 5.2 of Damm's theses. This is because it should still detect the phonetic errors after rearranging the columns, so the elements need to be renamed and the rows need to be rearranged accordingly.

Finally, here is the WTA quasigroup of order 32 for the Damm algorithm:

00 02 04 06 08 10 12 14 16 18 20 22 24 26 28 30 03 01 07 05 11 09 15 13 19 17 23 21 27 25 31 29 
02 00 06 04 10 08 14 12 18 16 22 20 26 24 30 28 01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31 
04 06 00 02 12 14 08 10 20 22 16 18 28 30 24 26 07 05 03 01 15 13 11 09 23 21 19 17 31 29 27 25 
06 04 02 00 14 12 10 08 22 20 18 16 30 28 26 24 05 07 01 03 13 15 09 11 21 23 17 19 29 31 25 27 
08 10 12 14 00 02 04 06 24 26 28 30 16 18 20 22 11 09 15 13 03 01 07 05 27 25 31 29 19 17 23 21 
10 08 14 12 02 00 06 04 26 24 30 28 18 16 22 20 09 11 13 15 01 03 05 07 25 27 29 31 17 19 21 23 
12 14 08 10 04 06 00 02 28 30 24 26 20 22 16 18 15 13 11 09 07 05 03 01 31 29 27 25 23 21 19 17 
14 12 10 08 06 04 02 00 30 28 26 24 22 20 18 16 13 15 09 11 05 07 01 03 29 31 25 27 21 23 17 19 
16 18 20 22 24 26 28 30 00 02 04 06 08 10 12 14 19 17 23 21 27 25 31 29 03 01 07 05 11 09 15 13 
18 16 22 20 26 24 30 28 02 00 06 04 10 08 14 12 17 19 21 23 25 27 29 31 01 03 05 07 09 11 13 15 
20 22 16 18 28 30 24 26 04 06 00 02 12 14 08 10 23 21 19 17 31 29 27 25 07 05 03 01 15 13 11 09 
22 20 18 16 30 28 26 24 06 04 02 00 14 12 10 08 21 23 17 19 29 31 25 27 05 07 01 03 13 15 09 11 
24 26 28 30 16 18 20 22 08 10 12 14 00 02 04 06 27 25 31 29 19 17 23 21 11 09 15 13 03 01 07 05 
26 24 30 28 18 16 22 20 10 08 14 12 02 00 06 04 25 27 29 31 17 19 21 23 09 11 13 15 01 03 05 07 
28 30 24 26 20 22 16 18 12 14 08 10 04 06 00 02 31 29 27 25 23 21 19 17 15 13 11 09 07 05 03 01 
30 28 26 24 22 20 18 16 14 12 10 08 06 04 02 00 29 31 25 27 21 23 17 19 13 15 09 11 05 07 01 03 
03 01 07 05 11 09 15 13 19 17 23 21 27 25 31 29 00 02 04 06 08 10 12 14 16 18 20 22 24 26 28 30 
01 03 05 07 09 11 13 15 17 19 21 23 25 27 29 31 02 00 06 04 10 08 14 12 18 16 22 20 26 24 30 28 
07 05 03 01 15 13 11 09 23 21 19 17 31 29 27 25 04 06 00 02 12 14 08 10 20 22 16 18 28 30 24 26 
05 07 01 03 13 15 09 11 21 23 17 19 29 31 25 27 06 04 02 00 14 12 10 08 22 20 18 16 30 28 26 24 
11 09 15 13 03 01 07 05 27 25 31 29 19 17 23 21 08 10 12 14 00 02 04 06 24 26 28 30 16 18 20 22 
09 11 13 15 01 03 05 07 25 27 29 31 17 19 21 23 10 08 14 12 02 00 06 04 26 24 30 28 18 16 22 20 
15 13 11 09 07 05 03 01 31 29 27 25 23 21 19 17 12 14 08 10 04 06 00 02 28 30 24 26 20 22 16 18 
13 15 09 11 05 07 01 03 29 31 25 27 21 23 17 19 14 12 10 08 06 04 02 00 30 28 26 24 22 20 18 16 
19 17 23 21 27 25 31 29 03 01 07 05 11 09 15 13 16 18 20 22 24 26 28 30 00 02 04 06 08 10 12 14 
17 19 21 23 25 27 29 31 01 03 05 07 09 11 13 15 18 16 22 20 26 24 30 28 02 00 06 04 10 08 14 12 
23 21 19 17 31 29 27 25 07 05 03 01 15 13 11 09 20 22 16 18 28 30 24 26 04 06 00 02 12 14 08 10 
21 23 17 19 29 31 25 27 05 07 01 03 13 15 09 11 22 20 18 16 30 28 26 24 06 04 02 00 14 12 10 08 
27 25 31 29 19 17 23 21 11 09 15 13 03 01 07 05 24 26 28 30 16 18 20 22 08 10 12 14 00 02 04 06 
25 27 29 31 17 19 21 23 09 11 13 15 01 03 05 07 26 24 30 28 18 16 22 20 10 08 14 12 02 00 06 04 
31 29 27 25 23 21 19 17 15 13 11 09 07 05 03 01 28 30 24 26 20 22 16 18 12 14 08 10 04 06 00 02 
29 31 25 27 21 23 17 19 13 15 09 11 05 07 01 03 30 28 26 24 22 20 18 16 14 12 10 08 06 04 02 00 
Michael
  • 46
  • 4