1 //! An implementation of the Verhoeff algorithm.
3 //! This checksum algorithm is not particularly common (the simpler and somewhat inferior Luhn
4 //! algorithm is much more widely used, e.g. in credit card numbers), but it definitely gets some
5 //! use; for example, India’s Aadhaar biometric identity system uses 12-digit numbers as the ID
6 //! number, with the final digit being a Verhoeff checksum.
8 //! Background reading: <https://en.wikipedia.org/wiki/Verhoeff_algorithm>
10 #![cfg_attr(not(feature = "std"), no_std)]
12 #[cfg(feature = "alloc")]
15 /// The multiplication table.
16 static D
: [[u8; 10]; 10] = [
17 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
18 [1, 2, 3, 4, 0, 6, 7, 8, 9, 5],
19 [2, 3, 4, 0, 1, 7, 8, 9, 5, 6],
20 [3, 4, 0, 1, 2, 8, 9, 5, 6, 7],
21 [4, 0, 1, 2, 3, 9, 5, 6, 7, 8],
22 [5, 9, 8, 7, 6, 0, 4, 3, 2, 1],
23 [6, 5, 9, 8, 7, 1, 0, 4, 3, 2],
24 [7, 6, 5, 9, 8, 2, 1, 0, 4, 3],
25 [8, 7, 6, 5, 9, 3, 2, 1, 0, 4],
26 [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
29 /// The permutation table.
30 static P
: [[u8; 10]; 8] = [
31 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
32 [1, 5, 7, 6, 2, 8, 3, 0, 9, 4],
33 [5, 8, 0, 3, 7, 9, 6, 1, 4, 2],
34 [8, 9, 1, 6, 0, 4, 3, 5, 2, 7],
35 [9, 4, 5, 3, 1, 2, 6, 8, 7, 0],
36 [4, 2, 8, 6, 5, 7, 3, 9, 0, 1],
37 [2, 7, 9, 3, 8, 0, 6, 4, 1, 5],
38 [7, 0, 4, 6, 9, 1, 3, 2, 5, 8],
41 /// The inverse table.
42 static INV
: [u8; 10] = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9];
44 /// Methods to perform the Verhoeff algorithm.
46 /// This is implemented for strings (which must contain only the ASCII digits 0–9) and u8 slices
47 /// (which must contain only the numbers 0–9).
52 /// use verhoeff::Verhoeff;
53 /// assert_eq!("12345".calculate_verhoeff_check_digit(), 1);
54 /// assert!("123451".validate_verhoeff_check_digit());
57 /// Confirm that the digits are valid and that the last is the correct check digit.
59 /// If any digit is not valid (a character other than ASCII digits 0–9 in strings, or a
60 /// number greater than nine in `u8` slices), it returns false.
61 fn validate_verhoeff_check_digit(&self) -> bool
;
63 /// Calculate the check digit for this input.
65 /// If any digit is not valid (a character other than ASCII digits 0–9 in strings, or a
66 /// number greater than nine in `u8` slices), it will panic.
68 /// The return value is an integer in the range 0–9.
69 fn calculate_verhoeff_check_digit(&self) -> u8;
72 impl Verhoeff
for str {
73 fn validate_verhoeff_check_digit(&self) -> bool
{
75 for (i
, digit
) in self.bytes().rev().enumerate() {
76 let digit
= match digit
{
77 b'0'..=b'9' => digit
- b'0',
80 c
= D
[c
][P
[i
% 8][digit
as usize] as usize] as usize;
86 fn calculate_verhoeff_check_digit(&self) -> u8 {
88 for (i
, digit
) in self.bytes().rev().enumerate() {
90 matches!(digit
, b'0'..=b'9'),
91 "number string contains a non-digit character",
93 c
= D
[c
][P
[(i
+ 1) % 8][(digit
- b'0') as usize] as usize] as usize;
100 impl Verhoeff
for [u8] {
101 fn validate_verhoeff_check_digit(&self) -> bool
{
103 for (i
, &digit
) in self.iter().rev().enumerate() {
107 c
= D
[c
][P
[i
% 8][digit
as usize] as usize] as usize;
113 fn calculate_verhoeff_check_digit(&self) -> u8 {
115 for (i
, &digit
) in self.iter().rev().enumerate() {
116 assert!(digit
< 10, "number sequence contains a value above nine");
117 c
= D
[c
][P
[(i
+ 1) % 8][digit
as usize] as usize] as usize;
124 /// A convenience trait for mutable methods pertaining to the Verhoeff algorithm.
128 doc
= " This is implemented for `String` and `Vec<u8>`.",
131 not(feature
= "alloc"),
132 doc
= " This is normally implemented for `String` and `Vec<u8>`, but you’ve you’ve disabled \
133 the alloc feature, so it’s not implemented on anything out of the box.",
135 pub trait VerhoeffMut
{
136 /// A convenience method to apply the Verhoeff algorithm in-place.
141 /// # #[cfg(feature = "alloc")] {
142 /// use verhoeff::VerhoeffMut;
143 /// let mut digits = String::from("12345");
144 /// digits.push_verhoeff_check_digit();
145 /// assert_eq!(digits, "123451");
149 /// Because this calls [`Verhoeff::calculate_verhoeff_check_digit`],
150 /// it will panic if the input is not comprised of appropriate digits.
151 fn push_verhoeff_check_digit(&mut self);
154 #[cfg(feature = "alloc")]
155 impl VerhoeffMut
for alloc
::string
::String
{
156 fn push_verhoeff_check_digit(&mut self) {
157 self.push(char::from(b'0' + self.calculate_verhoeff_check_digit()));
161 #[cfg(feature = "alloc")]
162 impl VerhoeffMut
for alloc
::vec
::Vec
<u8> {
163 /// A convenience method to apply the Verhoeff algorithm in-place.
168 /// use verhoeff::VerhoeffMut;
169 /// let mut digits = vec![1, 2, 3, 4, 5];
170 /// digits.push_verhoeff_check_digit();
171 /// assert_eq!(digits, [1, 2, 3, 4, 5, 1]);
174 /// Because this calls [`Verhoeff::calculate_verhoeff_check_digit`],
175 /// it will panic if the input is not comprised of appropriate digits.
176 fn push_verhoeff_check_digit(&mut self) {
177 self.push(self.calculate_verhoeff_check_digit());
181 /// A convenience method to calculate the Verhoeff algorithm check digit.
183 /// You can pass in a `&str` or a `&[u8]`, and get back a number in the range 0–9.
185 /// This is equivalent to calling [`Verhoeff::calculate_verhoeff_check_digit`].
186 /// It will panic if the input is not comprised of appropriate digits.
188 pub fn calculate
<T
: Verhoeff
+ ?Sized
>(input
: &T
) -> u8 {
189 input
.calculate_verhoeff_check_digit()
192 /// A convenience method to validate against the Verhoeff algorithm.
194 /// The check digit is presumed to be at the end of the input.
196 /// You can pass in a `&str` or a `&[u8]`.
198 /// This is equivalent to calling [`Verhoeff::validate_verhoeff_check_digit`].
199 /// It will return false if the input is not comprised of appropriate digits.
201 pub fn validate
<T
: Verhoeff
+ ?Sized
>(input
: &T
) -> bool
{
202 input
.validate_verhoeff_check_digit()
214 "84736430954837284567892",
216 let sans_check_digit
= &full
[..full
.len() - 1];
217 let expected_check_digit
= full
.chars().last().unwrap() as u32 as u8 - b'0';
218 let actual_check_digit
= calculate(sans_check_digit
);
219 if expected_check_digit
!= actual_check_digit
{
221 "On {}, expected check digit {} but calculated {}",
222 sans_check_digit
, expected_check_digit
, actual_check_digit
,
226 panic!("{} failed to validate", full
);
228 #[cfg(feature = "alloc")]
230 let mut new
= alloc
::string
::String
::from(sans_check_digit
);
231 new
.push_verhoeff_check_digit();
232 assert_eq!(new
, full
);
240 &[1, 4, 2, 8, 5, 7, 0],
241 &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 0],
243 8, 4, 7, 3, 6, 4, 3, 0, 9, 5, 4, 8, 3, 7, 2, 8, 4, 5, 6, 7, 8, 9, 2,
246 let sans_check_digit
= &full
[..full
.len() - 1];
247 let expected_check_digit
= *full
.iter().last().unwrap();
248 let actual_check_digit
= calculate(sans_check_digit
);
249 if expected_check_digit
!= actual_check_digit
{
251 "On {:?}, expected check digit {:?} but calculated {:?}",
252 sans_check_digit
, expected_check_digit
, actual_check_digit
,
256 panic!("{:?} failed to validate", full
);
258 #[cfg(feature = "alloc")]
260 let mut new
= alloc
::vec
::Vec
::from(sans_check_digit
);
261 new
.push_verhoeff_check_digit();
262 assert_eq!(new
, full
);
265 assert!(!validate("122451"));
266 assert!(!"128451".validate_verhoeff_check_digit());
267 assert!(![1, 2, 4, 3, 5, 1].validate_verhoeff_check_digit());
271 fn malformed_inputs_1() {
272 assert!([].validate_verhoeff_check_digit());
273 assert!("".validate_verhoeff_check_digit());
274 assert!(![1, 2, 3, 4, 10, 5, 6].validate_verhoeff_check_digit());
275 assert!(!validate("1234∞56"));
280 fn malformed_inputs_2() {
281 [1, 2, 3, 4, 10, 5, 6].calculate_verhoeff_check_digit();
286 fn malformed_inputs_3() {
287 calculate("1234∞56");