#2464·faker

no_NO.iban() is invalid when the account starts 0000: Postgiro uses Luhn

Author: feiiiiii5Created Sep 26, 2026Updated Sep 26, 2026

no_NO.iban() is invalid whenever the account number starts with 0000

A Norwegian account number is 11 digits: a 4-digit bank register number, six more digits, and a MOD11 check digit. The provider always applies MOD11 — and MOD11 is correct here. The problem is the register number: 0000 is not a bank register number, it is the Postgiro bank code, and a Postgiro account is a 7-digit number whose check digit is modulus 10 (Luhn). So when the random register number lands on 0000, the check digit is the wrong kind, and about 1 generated IBAN in 11,000 is invalid.

python
from faker import Faker
from stdnum.iban import validate

fake = Faker("no_NO")
Faker.seed(0)

for i in range(1, 2000):
    iban = fake.iban()
    try:
        validate(iban)
    except Exception as exc:
        print(i, iban, type(exc).__name__)
        break
1864 NO7900005649935 InvalidChecksum

Reading that value: the register number is 0000, and the remaining seven digits 5649935 are a Postgiro account number, whose check digit must satisfy Luhn. It does not, so validate raises InvalidChecksum.

MOD11 itself is right, so this is only the 0000 case

Worth saying explicitly, since the obvious suspicion is that the weights are wrong. They are not. faker/providers/bank/no_NO/__init__.py uses weights (6, 7, 8, 9, 4, 5, 6, 7, 8, 9) and takes sum % 11; the published standard states the same computation as weights 5432765432 with check digit 11 - (sum % 11). Since each of those is 11 - w, the two are the same number mod 11 — confirmed against the standard's own worked example (1234.45.6845 → check digit 4) and against stdnum.no.kontonr, which uses faker's form. Across 200,000 samples faker's check digit was always what MOD11 requires.

Why the test suite does not catch it

TestNoNo already has the right test — tests/providers/test_bank.py:456:

python
def test_iban_stdnum(self, faker, num_samples):
    for _ in range(num_samples):
        iban_validator.validate(faker.iban())

It is just under-powered for this defect. num_samples defaults to 100 (tests/providers/conftest.py:36-41), so a run hits a 0000 register number about 1% of the time — CI passes on this bug roughly 99% of runs. The hand-written is_valid_iban used by test_iban only checks mod-97 and cannot see it at all. The promise being broken is the bank provider's own, at faker/providers/bank/__init__.py:17-22: values "conform to some standard/format, are of the expected lengths, and have valid checksums (where applicable)".

What the check digit should be

stdnum.no.kontonr.compact strips a leading 0000 — commented "strip leading 0000 postgiro bank code" — and validate then branches on length: 7 digits go through luhn.validate, 11 digits through the MOD11 check. That module was written from the Perl GAAS::Norge::No::KontoNr, whose logic is quoted in python-stdnum#79, so the rule is not one implementation's private convention.

Two ways to fix it; I have no preference

  1. Apply Luhn when the register number is 0000, keeping Postgiro numbers valid. This is what stdnum expects, and it makes the existing test_iban_stdnum proof rather than 1%-likely proof.
  2. Never generate 0000 as the register number. The first four digits are the bank's register number and 0000 is the Postgiro code, so a bank provider arguably should not emit it. Simpler, at the cost of that number space.

Either way the test wants a seeded Postgiro case rather than 100 random draws.

Full 200,000-sample measurement
Faker.seed(20260927), every value through stdnum.iban.validate
BBANs starting 0000: 17      rejected: 16
  all rejections Postgiro-prefixed:            True
  all rejections Luhn-invalid after stripping: 16/16
observed 1 in 12,500; expected 1 in ~11,100 (P(0000)=1/10,000 x P(Luhn fails)~0.9)

The 17-vs-16 gap is the one Postgiro number that happened to satisfy Luhn too. Rejected values include NO4200008890498, NO5400007024487, NO2900007415362, NO2900000693650, NO5300005114187, NO9200007190352.

A related question I am less sure about

The same standard restricts digits 5 and 6 (the "account group"), which an unconstrained ########### cannot respect: 00 is "reserved for settlement accounts between banks", and 90-99 is "reserved for the banks' internal purposes". So ~10% of generated numbers land in 90-99 and ~1% in 00. I have no second implementation that checks this, so I am not claiming it as a defect — it may be out of scope, or fine for generated test data. Flagging it because it surfaced in the same investigation.


Provenance: main at 975dd2a1 and released faker==40.39.0, both reproducing the values above at the seeds given. Norwegian rules: Standard for Account Number in the Norwegian banking community, v10.