pub fn luhn(digits: &[u8]) -> bool {
if digits.is_empty() {
return false;
}
let mut sum = 0u32;
let mut double = false;
for &d in digits.iter().rev() {
let mut v = u32::from(d);
if double {
v *= 2;
if v > 9 {
v -= 9;
}
}
sum += v;
double = !double;
}
sum % 10 == 0
}
pub fn tckn(d: &[u8]) -> bool {
if d.len() != 11 || d[0] == 0 {
return false;
}
if d.iter().all(|&x| x == d[0]) {
return false;
}
let odd: u32 = [d[0], d[2], d[4], d[6], d[8]]
.iter()
.map(|&x| u32::from(x))
.sum();
let even: u32 = [d[1], d[3], d[5], d[7]].iter().map(|&x| u32::from(x)).sum();
let d10 = (odd * 7 + 100 - even) % 10;
let sum10: u32 = d[..10].iter().map(|&x| u32::from(x)).sum();
let d11 = sum10 % 10;
u32::from(d[9]) == d10 && u32::from(d[10]) == d11
}
pub fn vkn_check_digit(prefix: &[u8]) -> u8 {
debug_assert_eq!(prefix.len(), 9);
let mut sum = 0u32;
for (i, &v) in prefix.iter().enumerate() {
let tmp = (u32::from(v) + (9 - i as u32)) % 10;
let p = if tmp == 9 {
9
} else {
(tmp * 2u32.pow(9 - i as u32)) % 9
};
sum += p;
}
((10 - (sum % 10)) % 10) as u8
}
pub fn vkn(d: &[u8]) -> bool {
if d.len() != 10 {
return false;
}
if d.iter().all(|&x| x == d[0]) {
return false;
}
vkn_check_digit(&d[..9]) == d[9]
}
pub fn iban_mod97(s: &str) -> bool {
let cleaned: String = s.chars().filter(|c| !c.is_whitespace()).collect();
if cleaned.len() < 15 || cleaned.len() > 34 {
return false;
}
if !cleaned.chars().all(|c| c.is_ascii_alphanumeric()) {
return false;
}
let up = cleaned.to_ascii_uppercase();
if !up[..2].chars().all(|c| c.is_ascii_alphabetic())
|| !up[2..4].chars().all(|c| c.is_ascii_digit())
{
return false;
}
let (head, tail) = up.split_at(4);
let mut rem: u32 = 0;
for c in tail.chars().chain(head.chars()) {
rem = if c.is_ascii_digit() {
(rem * 10 + (c as u32 - '0' as u32)) % 97
} else {
(rem * 100 + (c as u32 - 'A' as u32 + 10)) % 97
};
}
rem == 1
}
pub fn iban_length_ok(country: &str, len: usize) -> bool {
const LENGTHS: &[(&str, usize)] = &[
("TR", 26),
("DE", 22),
("GB", 22),
("FR", 27),
("NL", 18),
("IT", 27),
("ES", 24),
("BE", 16),
("CH", 21),
("AT", 20),
];
match LENGTHS.iter().find(|(c, _)| *c == country) {
Some((_, expected)) => len == *expected,
None => (15..=34).contains(&len),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn digits(s: &str) -> Vec<u8> {
s.bytes().map(|b| b - b'0').collect()
}
#[test]
fn luhn_accepts_valid_and_rejects_tampered() {
assert!(luhn(&digits("4111111111111111")));
assert!(!luhn(&digits("4111111111111112")));
assert!(!luhn(&digits("")));
}
#[test]
fn tckn_accepts_derived_vector() {
assert!(tckn(&digits("12345678950")));
}
#[test]
fn tckn_accepts_independent_published_vector() {
assert!(tckn(&digits("10000000146")));
assert!(!tckn(&digits("10000000145")));
assert!(!tckn(&digits("10000000136")));
}
#[test]
fn tckn_rejects_bad_checkdigits_leading_zero_and_wrong_length() {
assert!(!tckn(&digits("12345678951"))); assert!(!tckn(&digits("12345678940"))); assert!(!tckn(&digits("02345678950"))); assert!(!tckn(&digits("1234567895"))); assert!(!tckn(&digits("123456789501"))); }
#[test]
fn tckn_rejects_all_same_digit() {
assert!(!tckn(&digits("11111111111")));
}
#[test]
fn vkn_roundtrips_its_own_checkdigit() {
let prefix = digits("456789123");
let check = vkn_check_digit(&prefix);
let mut full = prefix.clone();
full.push(check);
assert!(vkn(&full));
let mut bad = full.clone();
bad[9] = (bad[9] + 1) % 10;
assert!(!vkn(&bad));
}
#[test]
fn vkn_rejects_wrong_length() {
assert!(!vkn(&digits("12345678")));
assert!(!vkn(&digits("12345678901")));
}
#[test]
fn iban_mod97_accepts_published_example() {
assert!(iban_mod97("GB82WEST12345698765432"));
assert!(iban_mod97("GB82 WEST 1234 5698 7654 32")); assert!(iban_mod97("gb82west12345698765432")); assert!(!iban_mod97("GB82WEST12345698765433")); }
#[test]
fn iban_mod97_rejects_malformed() {
assert!(!iban_mod97("GB82"));
assert!(!iban_mod97("GB82WEST1234569876543212345678901234567890"));
assert!(!iban_mod97("GB82WEST!2345698765432"));
}
#[test]
fn iban_length_table_is_enforced_for_known_countries() {
assert!(iban_length_ok("TR", 26));
assert!(!iban_length_ok("TR", 25));
assert!(iban_length_ok("GB", 22));
assert!(iban_length_ok("ZZ", 20));
assert!(!iban_length_ok("ZZ", 40));
}
}