FA-72711 / Check-digit algorithms / Open access
Luhn mod N generation starts with factor one · case 01
Generated check characters fail validation.
ROOT CAUSE
Generation starts the factor at 1, the validation phase, although the check character is not yet present.
VERIFIED REPAIR
Start at factor 2 for the rightmost payload character.
Unsuccessful approach: Choosing the start factor from the payload length parity only works for half of the lengths.
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 = 1
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 ["1"]', ['1'], 'y'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None]], [['regression ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['regression ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1']], [['regression ["iu2qty"]', ['iu2qty'], 'k'], ['regression ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["um"]', ['um'], 'x'], ['partial-repair ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6']], [['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['regression ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None]], [['regression ["um"]', ['um'], 'x'], ['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2']]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression ["lyx7g3y"] | n | x | Failed |
| regression ["1"] | z | y | Failed |
| partial-repair ["s8zc"] | w | 5 | Failed |
| partial-repair ["i3wknpx64b"] | z | a | Failed |
| control ["0rr"] | q | q | Passed |
| control ["z"] | 1 | 1 | Passed |
| control ["zz"] | 2 | 2 | Passed |
| control ["abc-1"] | None | None | Passed |
SHA-256 / 5e59c88255dc93942ac5d936cd9afb377a4e763bb6c9741112affef65e5fcd51
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 if len(t) % 2 else 1
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 ["1"]', ['1'], 'y'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None]], [['regression ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['regression ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1']], [['regression ["iu2qty"]', ['iu2qty'], 'k'], ['regression ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["um"]', ['um'], 'x'], ['partial-repair ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6']], [['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['regression ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None]], [['regression ["um"]', ['um'], 'x'], ['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2']]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression ["lyx7g3y"] | x | x | Passed |
| regression ["1"] | y | y | Passed |
| partial-repair ["s8zc"] | w | 5 | Failed |
| partial-repair ["i3wknpx64b"] | z | a | Failed |
| control ["0rr"] | q | q | Passed |
| control ["z"] | 1 | 1 | Passed |
| control ["zz"] | 2 | 2 | Passed |
| control ["abc-1"] | None | None | Passed |
SHA-256 / 13443148cd54d76154d093045ec334b6c751c897d1cc4051b2a5da38afd82662
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 ["1"]', ['1'], 'y'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None]], [['regression ["hn3a6aj"]', ['hn3a6aj'], 'a'], ['regression ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1']], [['regression ["iu2qty"]', ['iu2qty'], 'k'], ['regression ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["um"]', ['um'], 'x'], ['partial-repair ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None], ['control ["0"]', ['0'], '0'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6']], [['regression ["bbw4ngt"]', ['bbw4ngt'], 's'], ['regression ["hgvs8gmf0"]', ['hgvs8gmf0'], 'j'], ['partial-repair ["s8zc"]', ['s8zc'], '5'], ['partial-repair ["i3wknpx64b"]', ['i3wknpx64b'], 'a'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2'], ['control ["abc-1"]', ['abc-1'], None], ['control [""]', [''], None]], [['regression ["um"]', ['um'], 'x'], ['regression ["w35lg7vo1l"]', ['w35lg7vo1l'], 'c'], ['partial-repair ["a5qsqld2"]', ['a5qsqld2'], 'r'], ['partial-repair ["60an"]', ['60an'], '9'], ['control ["ZZZZZZ"]', ['ZZZZZZ'], '6'], ['control ["0rr"]', ['0rr'], 'q'], ['control ["z"]', ['z'], '1'], ['control ["zz"]', ['zz'], '2']]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression ["lyx7g3y"] | x | x | Passed |
| regression ["1"] | y | y | Passed |
| partial-repair ["s8zc"] | 5 | 5 | Passed |
| partial-repair ["i3wknpx64b"] | a | a | Passed |
| control ["0rr"] | q | q | Passed |
| control ["z"] | 1 | 1 | Passed |
| control ["zz"] | 2 | 2 | Passed |
| control ["abc-1"] | None | None | Passed |
SHA-256 / 108df848c30b6b43819a8ba4a6810f8780fdd5b9f824bc49cbd4900c43456098
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.886715+00:00.
Case digest / ba96a34b910545dc055708bfd36e426e759d3d87b687bf24753f57a2d344b531