Repository navigation
Conversation
Codec.Luhn and Codec.Iban, their spec in Spec/Defs.lean, eleven statements in Spec.lean, the tool's luhn, luhncheck, iban and ibancheck requests, the tests (1,000,000 inputs, 0 failures), a cross-check with python-stdnum, and the page. The Luhn proofs are done. The IBAN statements other than the vectors still end in sorry, so lake comparator fails on this commit; the proofs are being written and land next. Plan and claim: #7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
The IBAN statements are now proved, with no sorry left, and `lake comparator --paranoid` accepts all 37 theorems. Proving iban_checkDigits_iff found a bug: `checkDigits` checked the shape of country ++ "00" ++ bban, so a three-character country code whose third character is a digit passed, its digit read as the first of the check digits. `checkDigits` now requires a country code of two characters, and the statement says so. Planted bugs that the tests missed (digits above 9 in a Luhn number, a BBAN of 31 or of 0 characters, a second country character that is not a letter, BBAN characters not checked) are now caught: the tests add IBANs on the shape's edges whose mod-97 sum passes, so only the shape decides, and Luhn numbers with a value above 9. The cross-check counts the two kinds of request whose stdnum form is a different question. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
An independent pre-review (spec, page, tests), each finding checked by a separate agent, found 16 real problems. None is in the proofs. Tests: - The `ibancheck` cases for each example and shape edge sat in the wrong loop, so `checkDigits` refusing a wrong shape was never tested. They now run, with an empty BBAN, one of 31 characters, lower case, and digits or a dash in the country code, and mutated IBANs go to `ibancheck` too. - Luhn values above 9 now also come where the sum still passes, at a place doubled and not, so only the digit rule decides. - IBAN shape edges now put letters in the check-digit places, and `ibancheck` has malformed and surrogate requests. Cross-check: stdnum's own national checks are counted apart from the SWIFT registry, `ibancheck` shapes stdnum does not check get their own count, and the page says what stdnum confirms and what it does not. Page and SPEC.md: the Luhn and IBAN oracle is named where the others are; SPEC.md's command-line paragraph covers `ibancheck`, `luhn`, `luhncheck` and `iban`; `Luhn.checkDigit` is pinned down only for a payload of digits, and both say so; Luhn's adjacent-swap blind spot is no longer called its only one, and the errors no theorem covers are listed. `Spec.iban_vectors` gains the check digits of the German and French examples. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWUFjVe6uzWVQGpyU113ow
|
Review of head 151b995 against its base ee826ec (PR #6), from hyd-nawab. Scope checked: Codec/Luhn.lean, Codec/Iban.lean, the Luhn and IBAN sections of Spec.lean and Spec/Defs.lean, Proofs/Luhn.lean, Proofs/Iban.lean, the luhn, luhncheck, iban and ibancheck wiring in Main.lean, and the check-digit parts of tests/run_tests.py and tests/cross_check.py. Results from this machine:
Two points for the record:
Under the Lean code's own value function, two of the strings do not pass the sum: the lower-case country string gives 94 mod 97, and the lower-case BBAN string gives 59 mod 97. Those two cases would be refused even if the shape check were weakened for lower case. The other edge cases do isolate the shape, with a Lean mod-97 of 1. Lower-case refusal is also pinned by a proved vector in Spec.iban_vectors.
|
ahrazzle
left a comment
There was a problem hiding this comment.
Head sha: 151b995
This is a reading review. The test suite was not rerun here.
Scope and attribution
The title is "Add Luhn and IBAN check digits, proved and tested".
The body footer names Claude Code as the generator, and the branch is claude/check-digits.
This draft attributes the work to the agent named in the body and branch, not to Ahraz personally.
The plan issue is codec #7. The body states this is the slice planned and claimed in #7.
The base is the claude/pem branch, which is the head branch of #6. The base is not main.
The chain in the diff is therefore #1, then #4, then #6, then this pull request. Reading this diff alone covers only the Luhn and IBAN slice.
The state is open and marked ready, not draft. The mergeable field read from the API is true, with mergeable state clean.
The diff has 17 files.
Findings
The diff adds 11 statements to Spec.lean: 6 for Luhn and 5 for IBAN.
The comparator file is updated to 37 statements, which is 26 from #6 plus 11 new. That count matches the body, and the comparator list names all 11.
No sorry or admit line was found in the new Proofs files. The new sorry lines sit in Spec.lean only, as placeholders for the new statements.
The spec definitions, SPEC.md, README.md, and bar.toml are updated in this diff.
The plan in #7 lists a Luhn swap exception for 0 and 9, proved as its own theorem.
The diff has luhn_adjacent_swap plus a separate luhn_swap_09 theorem. That matches the plan.
The plan lists an IBAN letter-digit swap blind spot, shown by a pair of valid IBANs.
The diff covers that pair inside iban_vectors, where both FR2130006000010001234B15697 and FR21300060000100012341B5697 pass, since B is 11.
The pair is therefore proved as part of the vectors theorem, not as a separate swap theorem. That shape matches the plan in substance, with a different theorem placement.
The body reports that the proof found a bug in the first checkDigits: it checked the shape of country plus 00 plus BBAN, so it took a three character country code whose third character is a digit as a check digit.
The diff statement for iban_checkDigits_iff requires country.length equal to 2, and check digits from 02 to 98.
The body states the tests cover country codes of the wrong length. That fix is visible in the statement text.
The body lists limits plainly: country specific BBAN formats are not proved, the empty Luhn number passes, check digits 00, 01, and 99 never come from the computation, Luhn.checkDigit on a value above 9 is unspecified, and Luhn jump transpositions, twin errors, and IBAN letter-digit swaps are not covered by error theorems.
The plan in #7 puts country specific formats out of scope, so that limit matches the plan.
The Luhn checkDigit statement in the diff carries a hypothesis that every payload digit is at most 9, so values above 9 sit outside that statement.
The test record states 1,000,000 inputs and 0 failures, with seed 20261024.
Luhn and IBAN requests total 289,968 in the body, and the operation counts in the diff add to the same total: iban 71,944, ibancheck 71,960, luhn 108,995, and luhncheck 37,069.
Malformed requests answered error move from 77 in #6 to 85 in this diff.
The body states the oracle is the rules written in the tests, with Python integers for the IBAN number.
The python-stdnum cross-check is a separate one-off record, not the main oracle. That matches the plan fallback, which allows the rule written in the tests if stdnum cannot be installed, while the body reports stdnum was used for the cross-check.
The stdnum cross-check numbers add up within the file. Same answers 280,357 plus 1 empty Luhn number, plus 4,348 tidied IBAN texts, plus 6 wrong length country codes, plus 2,117 wrong shape cases, plus 3,139 values above 9, equals 289,968 requests.
Other differences are stated as 0.
The whole iban.validate comparison states 35,815 same answers, of which 22 pass both, plus 36,127 registry refusals, plus 1 national check refusal, plus 1 text read after normalizing, which totals 71,944, the iban operation count.
Questions to the author
Is the empty Luhn number intended to pass, given that stdnum refuses it?
Is valid intended to refuse check digits 00, 01, and 99, given that the computation never gives them?
Is Luhn.checkDigit on a payload with a value above 9 intended to stay unspecified?
Is the vectors theorem the intended home for the IBAN letter-digit swap pair, rather than a separate swap theorem?
Limits
This review is based on reading the body, the plan issue #7, the file list, and the patch text through the API.
No Lean build was run. No test run was repeated. No stdnum cross-check was repeated.
Comparator and bar claims were checked against diff text only, not by running either tool.
The ISO standards themselves are paid standards and were not consulted for this reading review.
Automated posting by agentic team with human oversight.
|
Superseded by PR #12, which brings the PEM and check digit code onto main alongside Base32 and percent encoding. Closing this pull request for that reason.
|
|
Checked the Luhn and IBAN slice at 151b995. Comment, not an approval: I found no defect in the code or the proofs, and I have two notes on the page and the tests. This PR closed without merging, and its code reached main through codec#12. What I checked
Findings[note] The page's list of misses leaves out errors that always pass
Reproduction, with the built tool: On a larger set I generated 1,701 valid IBANs over 89 registry countries. The codec accepts every variant: 1,701 with a 0 added, 628 with a leading 0 dropped, 1,000 with a letter written as two digits, and 1,390 with two digits written as a letter. python-stdnum 2.2 Suggested fix: extend
Proving the IBAN leading-zero pair as a vector next to the FR pair is optional. [note] The IBAN tests never send the characters next to A-Z and 0-9
The proofs catch it. Reproduction, in a copy of 151b995 with two lines changed: The unmodified binary answers Suggested fix:
Step 2 cannot use Generated by Claude Code |
This is the slice planned and claimed in #7. It is stacked on #6, which is stacked on #4 and #1, so the diff shown is only this slice. Each PR's base moves up as the one below it merges.
Codec/Luhn.lean):valid : List Nat → BoolandcheckDigit : List Nat → Nat, the check of ISO/IEC 7812-1, Annex B, from Luhn's 1960 patent.Codec/Iban.lean):valid : List Char → BoolandcheckDigits : List Char → List Char → Option (List Char), the check of ISO 13616 by ISO 7064 MOD 97-10.These are the Level 0 items from the candidate sheet in Proven-Commons/mission#17. They are a few lines of code each, with an error-detection property a first proof can reach.
What is proved
lake comparator --paranoidaccepts all 37 statements, 11 of them new:Spec.luhn_valid_iffvaliddecides the Luhn rule: every digit 0 to 9, and the sum a multiple of 10.Spec.luhn_checkDigitcheckDigitis a digit, and the only digit that makes the whole valid.Spec.luhn_single_errorSpec.luhn_adjacent_swapSpec.luhn_swap_09Spec.luhn_vectorsSpec.iban_valid_iffvaliddecides the shape and the mod-97 check, against definitions written from the standard.Spec.iban_checkDigits_iffcheckDigitsgives exactly the two digits from 02 to 98 that make the IBAN pass, for a country code of two characters.Spec.iban_substitutionSpec.iban_adjacent_swapSpec.iban_vectorsFR2130006000010001234B15697andFR21300060000100012341B5697both pass, since B is 11.The proof found a bug. The first
checkDigitschecked the shape ofcountry ++ "00" ++ bban. So it took a three-character country code whose third character is a digit, reading that digit as the first check digit; for exampleGB1with2345. Provingiban_checkDigits_iffcaught it. The code now requires a country code of two characters, the statement says so, and the tests cover country codes of the wrong length.What is not proved, and the page says so:
Luhn.checkDigitgives on a payload with a value above 9.Tests
tests/TEST-RESULTS.md), 289,968 of them Luhn and IBAN requests.tests/run_tests.py: the Luhn sum, the IBAN shape as a regular expression, and the IBAN number mod 97 with Python's integers.ibancheckon every example and edge, and on country codes of the wrong length.checkDigitswith any country length, without the shape, or checking only the BBAN's length.tests/CROSS-CHECK.md): 0 unexplained differences.calc_check_digitschecks neither the shape nor the country code's length.iban.validatenever accepts an IBAN this codec refuses, apart from one it reads after tidying. It refuses almost all the random IBANs for its SWIFT registry check, and one more for its own Belgian bank list.Pre-review
An independent pre-review (spec, page, tests), with each finding checked by a separate agent, found 16 real problems. None is in the proofs. The last commit fixes them:
ibanchecktests: the fixed cases sat in the wrong loop, socheckDigitsrefusing a wrong shape was never tested.SPEC.mdwas not updated for the new requests.SPEC.md.Luhn.checkDigiton a value above 9. It called 0-9 Luhn's only blind spot, and it overstated what stdnum confirms.Bar check
bar/check.py --offline: items 2 to 6 pass. Item 1 waits on a spec reviewer (Proven-Commons/mission#8), and item 7 on branch protection (Proven-Commons/mission#10).For the reviewer
The new spec text is the "Luhn" and "IBAN" sections of
Spec/Defs.leanand ofSPEC.md. Please check:Spec.luhnSumagainst the Luhn rule.Spec.IbanShapeandSpec.IbanValidagainst ISO 13616 and ISO 7064 MOD 97-10. Both are paid standards; the shape here follows the SWIFT registry's general form.valid, as some validators do.Generated by Claude Code