1// Package luhn implements the Luhn mod-10 checksum (Hans Peter Luhn, 1954) as2// a pure, reusable package: the check used by credit-card numbers, IMEIs and3// many national ID schemes to catch typos and single-digit transpositions.4//5// It is a *checksum*, not a security primitive: it detects every single-digit6// error and almost every adjacent transposition, but it is trivial to forge and7// says nothing about whether an identifier actually exists. Never use it to8// authorize anything on-chain.9//10// No clocks, no randomness, no chain imports — same input, same output.11//12// A live demo of this package is at13// [r/moul/x/daily/luhndemo](/r/moul/x/daily/luhndemo/v0).14package luhn1516import (17 "strings"18)1920// MaxLen bounds an input so validation gas stays predictable.21const MaxLen = 642223// Valid reports whether s carries a correct Luhn check digit.24//25// Spaces and hyphens are ignored, so "4539 1488 0343 6467" and26// "4539-1488-0343-6467" both work. Any other non-digit makes it false, as does27// an empty/1-digit input or one longer than MaxLen. A string of all zeros is28// technically Luhn-valid and is accepted — reject it in the caller if your29// domain needs to.30func Valid(s string) bool {31 digits, ok := clean(s)32 if !ok || len(digits) < 2 {33 return false34 }35 return sum(digits)%10 == 036}3738// CheckDigit returns the digit that must be appended to payload to make the39// whole string Luhn-valid, and whether the payload was usable.40func CheckDigit(payload string) (int, bool) {41 digits, ok := clean(payload)42 if !ok || len(digits) == 0 {43 return 0, false44 }45 // The check digit sits in the "doubled" position of the final number, so46 // compute the sum as if a 0 had already been appended.47 total := sum(append(digits, 0))48 return (10 - total%10) % 10, true49}5051// Append returns payload with its Luhn check digit appended (digits only,52// separators stripped), and whether the payload was usable.53func Append(payload string) (string, bool) {54 d, ok := CheckDigit(payload)55 if !ok {56 return "", false57 }58 digits, _ := clean(payload)59 var b strings.Builder60 for _, x := range digits {61 b.WriteByte(byte('0' + x))62 }63 b.WriteByte(byte('0' + d))64 return b.String(), true65}6667// clean turns s into its digit values, ignoring spaces and hyphens. ok is false68// if any other character appears or the input exceeds MaxLen.69func clean(s string) ([]int, bool) {70 if len(s) > MaxLen {71 return nil, false72 }73 digits := make([]int, 0, len(s))74 for i := 0; i < len(s); i++ {75 c := s[i]76 switch {77 case c >= '0' && c <= '9':78 digits = append(digits, int(c-'0'))79 case c == ' ' || c == '-':80 // separator: ignore81 default:82 return nil, false83 }84 }85 return digits, true86}8788// sum computes the Luhn total: walking right to left, every second digit is89// doubled, and a double of 10 or more has 9 subtracted (equivalent to adding90// its two decimal digits).91func sum(digits []int) int {92 total := 093 double := false94 for i := len(digits) - 1; i >= 0; i-- {95 d := digits[i]96 if double {97 d *= 298 if d > 9 {99 d -= 9100 }101 }102 total += d103 double = !double104 }105 return total106}107Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.