|
1 | 1 | # -*- coding: UTF-8 -*- |
2 | 2 | """Luhn Codec - Luhn Mod N checksum algorithm. |
3 | 3 |
|
4 | | -The Luhn algorithm, also known as the "modulus 10" algorithm, is a simple checksum |
5 | | -formula used to validate identification numbers (e.g. credit card numbers, IMEI |
6 | | -numbers). Encoding appends a check character; decoding verifies the check character |
7 | | -and strips it. |
8 | | -
|
9 | | -The Luhn Mod N generalization extends the algorithm to alphabets of arbitrary size N. |
10 | | -When called as 'luhn' or 'luhn-10', the standard decimal alphabet (0-9, N=10) is |
11 | | -used. When called as 'luhn-<N>' for 2 ≤ N ≤ 36, the first N characters of |
12 | | -'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' form the alphabet. |
| 4 | +This is a codec for computing checksums, for use with other codecs in encoding chains. |
13 | 5 |
|
14 | 6 | This codec: |
15 | | -- en/decodes strings from str to str |
16 | | -- en/decodes strings from bytes to bytes |
17 | | -- decodes file content to str (read) |
18 | | -- encodes file content from str to bytes (write) |
19 | | -
|
20 | | -Reference: https://en.wikipedia.org/wiki/Luhn_algorithm |
21 | | - https://bitcoinwiki.org/wiki/luhn-mod-n-algorithm |
| 7 | +- transforms strings from str to str |
| 8 | +- transforms strings from bytes to bytes |
| 9 | +- transforms file content from str to bytes (write) |
22 | 10 | """ |
23 | 11 | from ..__common__ import * |
24 | 12 |
|
25 | 13 |
|
26 | | -__examples__ = { |
27 | | - 'enc(luhn|luhn-10|luhn10)': { |
28 | | - '7992739871': '79927398713', |
29 | | - '': '', |
30 | | - '0': '00', |
31 | | - '1': '18', |
32 | | - }, |
33 | | - 'dec(luhn|luhn-10|luhn10)': { |
34 | | - '79927398713': '7992739871', |
35 | | - '': '', |
36 | | - '00': '0', |
37 | | - '18': '1', |
38 | | - }, |
39 | | - 'enc-dec(luhn)': ['123456789', '0' * 10, '9999999999999999'], |
40 | | - 'enc-dec(luhn-16)': ['0123456789ABCDEF', 'DEADBEEF'], |
41 | | - 'enc-dec(luhn-36)': ['HELLO', 'WORLD123'], |
42 | | -} |
43 | | - |
44 | | -_FULL_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
45 | | - |
46 | | - |
47 | | -def _luhn_encode(n=""): |
48 | | - mod = n if isinstance(n, int) else 10 |
49 | | - alphabet = _FULL_ALPHABET[:mod] |
50 | | - |
51 | | - def _encode(text, errors="strict"): |
52 | | - text = ensure_str(text).upper() if mod > 10 else ensure_str(text) |
53 | | - if not text: |
| 14 | +def luhn(n=""): |
| 15 | + alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[:(mod := n if isinstance(n, int) else 10)] |
| 16 | + def encode(data, errors="strict"): |
| 17 | + total, data = 0, "".join(c if c in alphabet else handle_error("luhn", errors, kind="character")(c, i, data) \ |
| 18 | + for i, c in enumerate(data)) |
| 19 | + if not (data := ensure_str(data).upper() if mod > 10 else ensure_str(data)): |
54 | 20 | return "", 0 |
55 | | - for pos, c in enumerate(text): |
56 | | - if c not in alphabet: |
57 | | - handle_error("luhn", errors, kind="character")(c, pos, text) |
58 | | - total = 0 |
59 | | - for i, c in enumerate(reversed(text)): |
| 21 | + for i, c in enumerate(reversed(data)): |
60 | 22 | code = alphabet.index(c) |
61 | 23 | if i % 2 == 0: |
62 | 24 | d = code * 2 |
63 | 25 | code = d % mod + d // mod |
64 | 26 | total += code |
65 | 27 | check = (mod - total % mod) % mod |
66 | | - return text + alphabet[check], len(b(text)) |
67 | | - |
68 | | - return _encode |
69 | | - |
70 | | - |
71 | | -def _luhn_decode(n=""): |
72 | | - mod = n if isinstance(n, int) else 10 |
73 | | - alphabet = _FULL_ALPHABET[:mod] |
74 | | - |
75 | | - def _decode(text, errors="strict"): |
76 | | - text = ensure_str(text).upper() if mod > 10 else ensure_str(text) |
77 | | - if not text: |
78 | | - return "", 0 |
79 | | - for pos, c in enumerate(text): |
80 | | - if c not in alphabet: |
81 | | - handle_error("luhn", errors, decode=True, kind="character")(c, pos, text) |
82 | | - total = 0 |
83 | | - for i, c in enumerate(reversed(text)): |
84 | | - code = alphabet.index(c) |
85 | | - if i % 2 == 1: |
86 | | - d = code * 2 |
87 | | - code = d % mod + d // mod |
88 | | - total += code |
89 | | - if total % mod != 0: |
90 | | - handle_error("luhn", errors, decode=True)(text[-1], len(text) - 1, text[:-1]) |
91 | | - return text[:-1], len(b(text)) |
| 28 | + return alphabet[check], len(b(data)) |
| 29 | + return encode |
92 | 30 |
|
93 | | - return _decode |
94 | 31 |
|
| 32 | +add("luhn", luhn, pattern=r"^luhn[-_]?(\d{1,2})?$", guess=None) |
95 | 33 |
|
96 | | -add("luhn", _luhn_encode, _luhn_decode, pattern=r"^luhn[-_]?(\d{1,2})?$", guess=None) |
0 commit comments