FAILURE MAP
← Case archive

FA-72706 / Check-digit algorithms / Open access

Luhn mod N folds addends in base ten · case 01

Codes containing letters in doubled positions receive the wrong check character.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The addend is folded as addend // 10 + addend % 10, the decimal Luhn rule, instead of in base 36.

VERIFIED REPAIR

Fold every addend in base N: addend // 36 + addend % 36.

Unsuccessful approach: Keeping only addend % 36 drops the carry of doubled codepoints 18 and above.

Case contract

Luhn mod N check-character generation over the base-36 alphabet 0-9a-z (input is lower-cased; empty input or any other character returns None). From the rightmost payload character the factor alternates 2,1,2,...; each addend factor * codepoint is folded as addend // 36 + addend % 36; the check is the alphabet character at (36 - sum % 36) % 36.

Why this case matters

Voucher and licence-key systems protect alphanumeric codes with Luhn mod N check characters.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(s):
    A = '0123456789abcdefghijklmnopqrstuvwxyz'
    n = len(A)
    t = s.lower()
    if not t or any(ch not in A for ch in t):
        return None
    factor = 2
    total = 0
    for ch in reversed(t):
        addend = factor * A.index(ch)
        factor = 1 if factor == 2 else 2
        total += addend // 10 + addend % 10
    return A[(n - total % n) % n]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["lyx7g3y"]', ['lyx7g3y'], 'x'], ['regression ["g22"]', ['g22'], 'y'], ['partial-repair ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['regression ["iu2qty"]', ['iu2qty'], 'k'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["60an"]', ['60an'], '9'], ['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['partial-repair ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["nx"]', ['nx'], 'i'], ['regression ["um"]', ['um'], 'x'], ['partial-repair ["0rr"]', ['0rr'], 'q'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['regression ["w"]', ['w'], '7'], ['partial-repair ["yl5unzgy"]', ['yl5unzgy'], '2'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression ["lyx7g3y"]ixFailed
regression ["g22"]pyFailed
partial-repair ["hn3a6aj"]2aFailed
control ["1"]yyPassed
control ["s8zc"]55Passed
control ["abc-1"]NoneNonePassed
control [""]NoneNonePassed
control ["0"]00Passed

SHA-256 / bf795f1a246d004c0831192bd376ec098c5433afa3e99a25c7e71a8f2d8d0936

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(s):
    A = '0123456789abcdefghijklmnopqrstuvwxyz'
    n = len(A)
    t = s.lower()
    if not t or any(ch not in A for ch in t):
        return None
    factor = 2
    total = 0
    for ch in reversed(t):
        addend = factor * A.index(ch)
        factor = 1 if factor == 2 else 2
        total += addend % n
    return A[(n - total % n) % n]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["lyx7g3y"]', ['lyx7g3y'], 'x'], ['regression ["g22"]', ['g22'], 'y'], ['partial-repair ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['regression ["iu2qty"]', ['iu2qty'], 'k'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["60an"]', ['60an'], '9'], ['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['partial-repair ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["nx"]', ['nx'], 'i'], ['regression ["um"]', ['um'], 'x'], ['partial-repair ["0rr"]', ['0rr'], 'q'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['regression ["w"]', ['w'], '7'], ['partial-repair ["yl5unzgy"]', ['yl5unzgy'], '2'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression ["lyx7g3y"]0xFailed
regression ["g22"]yyPassed
partial-repair ["hn3a6aj"]baFailed
control ["1"]yyPassed
control ["s8zc"]55Passed
control ["abc-1"]NoneNonePassed
control [""]NoneNonePassed
control ["0"]00Passed

SHA-256 / 7747ed393335e843b42cc56fffa42ff9c432f45e8d2e402c1bb4732f2638345d

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(s):
    A = '0123456789abcdefghijklmnopqrstuvwxyz'
    n = len(A)
    t = s.lower()
    if not t or any(ch not in A for ch in t):
        return None
    factor = 2
    total = 0
    for ch in reversed(t):
        addend = factor * A.index(ch)
        factor = 1 if factor == 2 else 2
        total += addend // n + addend % n
    return A[(n - total % n) % n]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression ["lyx7g3y"]', ['lyx7g3y'], 'x'], ['regression ["g22"]', ['g22'], 'y'], ['partial-repair ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['regression ["iu2qty"]', ['iu2qty'], 'k'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["60an"]', ['60an'], '9'], ['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['partial-repair ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["nx"]', ['nx'], 'i'], ['regression ["um"]', ['um'], 'x'], ['partial-repair ["0rr"]', ['0rr'], 'q'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']], [['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['regression ["w"]', ['w'], '7'], ['partial-repair ["yl5unzgy"]', ['yl5unzgy'], '2'], ['control ["1"]', ['1'], 'y'], ['control ["s8zc"]', ['s8zc'], '5'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0']]]
for label, args, expected in fixtures[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression ["lyx7g3y"]xxPassed
regression ["g22"]yyPassed
partial-repair ["hn3a6aj"]aaPassed
control ["1"]yyPassed
control ["s8zc"]55Passed
control ["abc-1"]NoneNonePassed
control [""]NoneNonePassed
control ["0"]00Passed

SHA-256 / 7127e651bec052ebb53f1f2a7618ed97f90992cffb92ef166b2942154e2dfb1a

Verification & scope

A deterministic, bounded teaching model of the named scheme under the stated contract; not a certified validator. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:48:40.886675+00:00.

Case digest / 9774d666ac6cca24d748626e618b25d3ecf107e073cce7eae508872008fcd5c0