Luhn mod N algorithm


The Luhn mod N algorithm is an extension to the Luhn algorithm that allows it to work with sequences of values in any even-numbered base. This can be useful when a check digit is required to validate an identification string composed of letters, a combination of letters and digits or any arbitrary set of characters where is divisible by 2.

Informal explanation

Original algorithm (Luhn mod 10)

The original Luhn algorithm was designed by Hans Peter Luhn and patented in 1960. It is a check digit algorithm that is intended to catch most common input errors by checking the algorithm's calculation of the digits of an identification number against the final digit. One particularly common use of the Luhn algorithm is in credit cards; the final digit of a 16-digit credit card number is the output of the Luhn algorithm applied to the first 15 digits. Government ID numbers, bank account numbers, ISBN numbers and many other identification numbers use the Luhn algorithm or a small modification of it.
The original Luhn algorithm is a special case of the Luhn mod algorithm, where equals 10. It takes in a string of digits, reverses the string, multiplies every other digit by two, then sums all of them. The final digit of this sum should be the same as the final digit of the string. The goal of the Luhn algorithm is to detect common input errors when entering a string of digits into a computer. It catches all substitution errors, where one digit is accidentally replaced by another, as well as most transposition errors, with 90 vs. 09 being the sole exception. Although it is a hash function, it is not intended to be cryptographically secure, nor to detect malicious errors or fraud.

Extending to mod N

The original Luhn algorithm is called the "mod 10" algorithm because it performs modular arithmetic on a 10-digit system. The check digit is generated by summing up the Luhn algorithm and taking the result modulo 10, which is equivalent to the remainder left over when dividing by 10, or the "ones" digit of the number. The same basic idea can be applied to an arbitrary system of N ordered characters.
The Luhn mod N algorithm generates a check digit within the same range of valid characters as the input string. For example, if the algorithm is applied to a string of lower-case letters, the check character will also be a lower-case letter. Apart from this distinction, it resembles very closely the original algorithm.
The main idea behind the extension is that the full set of valid input characters is mapped to a list of code-points. The algorithm processes the input string by converting each character to its associated code-point and then performing the computations in mod N. Finally, the resulting check code-point is mapped back to obtain its corresponding check character.

Limitation

The Luhn mod N algorithm only works where is divisible by 2. This is because there is an operation to correct the value of a position after doubling its value which does not work where is not divisible by 2. For applications using the ISO basic Latin alphabet this is not a problem, since a string of same-case letters has 26 code-points. Adding decimal characters adds a further 10, and adding the other case adds a further 26, maintaining an divisible by 2 in both cases.

Explanation

The second step in the Luhn algorithm re-packs the doubled value of a position into the original digit's base by adding together the individual digits in the doubled value when written in base. This step results in even numbers if the doubled value is less than or equal to, and odd numbers if the doubled value is greater than. For example, in decimal applications where is 10, original values between 0 and 4 result in even numbers and original values between 5 and 9 result in odd numbers, effectively re-packing the doubled values between 0 and 18 into a single distinct result between 0 and 9.
Where an is used that is not divisible by 2 this step returns even numbers for doubled values greater than which cannot be distinguished from doubled values less than or equal to.

Outcome

The algorithm will neither detect all single-digit errors nor all transpositions of adjacent digits if an is used that is not divisible by 2. As these detection capabilities are the algorithm's primary strengths, the algorithm is weakened almost entirely by this limitation. The Luhn mod N algorithm odd variation enables applications where is not divisible by 2 by replacing the doubled value at each position with the remainder after dividing the position's value by which gives odd number remainders consistent with the original algorithm design.

Mapping characters to code-points

Initially, a mapping between valid input characters and code-points must be created. For example, consider that the valid characters are the lower-case letters from a to f. Therefore, a suitable mapping would be:
Characterabcdef
Code-point012345

Note that the order of the characters is completely irrelevant. This other mapping would also be acceptable :
Characterceafbd
Code-point012345

It is also possible to intermix letters and digits. For example, this mapping would be appropriate for lower-case hexadecimal digits:
Character0123456789abcdef
Code-point0123456789101112131415

Algorithm in C#

Assuming the following functions are defined:

///
/// This can be any string of characters.
///

private const string CodePoints = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private int NumberOfValidInputCharacters => CodePoints.Length;
private int CodePointFromCharacter => CodePoints.IndexOf;
private char CharacterFromCodePoint => CodePoints;

The function to generate a check character is:

char GenerateCheckCharacter

And the function to validate a string is:

bool ValidateCheckCharacter

Algorithm in Java

Assuming the following functions are defined:

int codePointFromCharacter
char characterFromCodePoint
int numberOfValidInputCharacters

The function to generate a check character is:

char generateCheckCharacter

And the function to validate a string is:

boolean validateCheckCharacter

Algorithm in JavaScript

Assuming the following functions are defined:

const codePoints = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
//This can be any string of permitted characters
function numberOfValidInputCharacters
function codePointFromCharacter
function characterFromCodePoint

The function to generate a check character is:

function generateCheckCharacter

And the function to validate a string is:

function validateCheckCharacter

Example

Generation

Consider the above set of valid input characters and the example input string. To generate the check character, start with the last character in the string and move left doubling every other code-point. The "digits" of the code-points as written in base 6 should then be summed up:
Characterabcdef
Code-point012345
Double26
10
10
14
Reduce0221 + 041 + 4
Sum of digits022145

The total sum of digits is 14. The number that must be added to obtain the next multiple of 6 is 4. This is the resulting check code-point. The associated check character is e.

Validation

The resulting string can then be validated by using a similar procedure:
Characterabcdefe
Code-point0123454
Double26
10
10
14
Reduce0221 + 041 + 44
Sum of digits0221454

The total sum of digits is 18. Since it is divisible by 6, the check character is valid.

Implementation

The mapping of characters to code-points and back can be implemented in a number of ways. The simplest approach is to use ASCII code arithmetic. For example, given an input set of 0 to 9, the code-point can be calculated by subtracting the ASCII code for '0' from the ASCII code of the desired character. The reverse operation will provide the reverse mapping. Additional ranges of characters can be dealt with by using conditional statements.
Non-sequential sets can be mapped both ways using a hard-coded switch/case statement. A more flexible approach is to use something similar to an associative array. For this to work, a pair of arrays is required to provide the two-way mapping.
An additional possibility is to use an array of characters where the array indexes are the code-points associated with each character. The mapping from character to code-point can then be performed with a linear or binary search. In this case, the reverse mapping is just a simple array lookup.

Weakness

This extension shares the same weakness as the original algorithm, namely, it cannot detect the transposition of the sequence to . This is equivalent to the transposition of 09 to 90. On a positive note, the larger the set of valid input characters, the smaller the impact of the weakness.