Skip to content

Commit db19ffe

Browse files
authored
Merge pull request #464 from DucMinhNe/fix/isin-checksum
fix: correct broken ISIN checksum (validator accepted any check digit)
2 parents d23d964 + 474496d commit db19ffe

2 files changed

Lines changed: 29 additions & 6 deletions

File tree

‎src/validators/finance.py‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,32 @@ def _cusip_checksum(cusip: str):
3232

3333

3434
def _isin_checksum(value: str):
35-
check, val = 0, None
35+
# The check digit (last character) is always numeric.
36+
if not value[-1].isdecimal():
37+
return False
3638

39+
# Expand the code into a string of digits, mapping each letter to its
40+
# two-digit value (A=10, ..., Z=35) and leaving digits unchanged.
41+
digits = ""
3742
for idx in range(12):
3843
c = value[idx]
3944
if c >= "0" and c <= "9" and idx > 1:
40-
val = ord(c) - ord("0")
45+
digits += c
4146
elif c >= "A" and c <= "Z":
42-
val = 10 + ord(c) - ord("A")
47+
digits += str(10 + ord(c) - ord("A"))
4348
elif c >= "a" and c <= "z":
44-
val = 10 + ord(c) - ord("a")
49+
digits += str(10 + ord(c) - ord("a"))
4550
else:
4651
return False
4752

53+
# Luhn checksum over the expanded digit string: starting from the
54+
# rightmost digit, double every second digit and sum the resulting digits.
55+
check = 0
56+
for idx, c in enumerate(reversed(digits)):
57+
val = ord(c) - ord("0")
4858
if idx & 1:
4959
val += val
60+
check += (val // 10) + (val % 10)
5061

5162
return (check % 10) == 0
5263

‎tests/test_finance.py‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,25 @@ def test_returns_failed_validation_on_invalid_cusip(value: str):
2424
# ==> ISIN <== #
2525

2626

27-
@pytest.mark.parametrize("value", ["US0004026250", "JP000K0VF054", "US0378331005"])
27+
@pytest.mark.parametrize("value", ["US0004026250", "US0378331005", "GB0002634946", "DE000BAY0017"])
2828
def test_returns_true_on_valid_isin(value: str):
2929
"""Test returns true on valid isin."""
3030
assert isin(value)
3131

3232

33-
@pytest.mark.parametrize("value", ["010378331005", "XCVF", "00^^^1234", "A000009"])
33+
@pytest.mark.parametrize(
34+
"value",
35+
[
36+
"010378331005",
37+
"XCVF",
38+
"00^^^1234",
39+
"A000009",
40+
# valid length and characters but wrong check digit
41+
"US0378331006",
42+
"US0004026251",
43+
"GB0002634947",
44+
],
45+
)
3446
def test_returns_failed_validation_on_invalid_isin(value: str):
3547
"""Test returns failed validation on invalid isin."""
3648
assert isinstance(isin(value), ValidationError)

0 commit comments

Comments
 (0)