@stll/stdnum 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +11 -9
  2. package/dist/_checksums/keccak.d.ts +5 -0
  3. package/dist/_checksums/keccak.js +118 -0
  4. package/dist/_checksums/keccak.js.map +1 -0
  5. package/dist/_checksums/mod112.js.map +1 -1
  6. package/dist/_checksums/sha256.d.ts +5 -0
  7. package/dist/_checksums/sha256.js +164 -0
  8. package/dist/_checksums/sha256.js.map +1 -0
  9. package/dist/_checksums/verhoeff.js.map +1 -1
  10. package/dist/_util/arabic.js.map +1 -1
  11. package/dist/_util/clean.js.map +1 -1
  12. package/dist/_util/date.js.map +1 -1
  13. package/dist/ad/nrt.js.map +1 -1
  14. package/dist/ae/eid.js.map +1 -1
  15. package/dist/ai/tin.js.map +1 -1
  16. package/dist/al/nipt.js.map +1 -1
  17. package/dist/am/tin.js.map +1 -1
  18. package/dist/ar/cbu.js.map +1 -1
  19. package/dist/ar/cuit.js.map +1 -1
  20. package/dist/ar/dni.js.map +1 -1
  21. package/dist/at/businessid.js.map +1 -1
  22. package/dist/at/vnr.js.map +1 -1
  23. package/dist/au/abn.js.map +1 -1
  24. package/dist/au/tfn.js.map +1 -1
  25. package/dist/az/voen.js.map +1 -1
  26. package/dist/ba/jmbg.js.map +1 -1
  27. package/dist/be/bis.js.map +1 -1
  28. package/dist/be/nn.js.map +1 -1
  29. package/dist/be/vat.js.map +1 -1
  30. package/dist/bg/egn.js.map +1 -1
  31. package/dist/bg/vat.js.map +1 -1
  32. package/dist/bh/cpr.js.map +1 -1
  33. package/dist/bic.js.map +1 -1
  34. package/dist/br/cnpj.js.map +1 -1
  35. package/dist/br/cpf.js.map +1 -1
  36. package/dist/by/unp.js.map +1 -1
  37. package/dist/bz/tin.js.map +1 -1
  38. package/dist/ch/uid.js.map +1 -1
  39. package/dist/ch/vat.js.map +1 -1
  40. package/dist/cl/rut.js.map +1 -1
  41. package/dist/cn/ric.js.map +1 -1
  42. package/dist/cn/uscc.js.map +1 -1
  43. package/dist/cr/cpf.js.map +1 -1
  44. package/dist/crypto/btcbase58.d.ts +11 -0
  45. package/dist/crypto/btcbase58.js +76 -0
  46. package/dist/crypto/btcbase58.js.map +1 -0
  47. package/dist/crypto/btcbech32.d.ts +19 -0
  48. package/dist/crypto/btcbech32.js +145 -0
  49. package/dist/crypto/btcbech32.js.map +1 -0
  50. package/dist/crypto/eth.d.ts +11 -0
  51. package/dist/crypto/eth.js +63 -0
  52. package/dist/crypto/eth.js.map +1 -0
  53. package/dist/crypto/mod.d.ts +12 -0
  54. package/dist/crypto/mod.js +16 -0
  55. package/dist/crypto/mod.js.map +1 -0
  56. package/dist/crypto/wallet.d.ts +10 -0
  57. package/dist/crypto/wallet.js +55 -0
  58. package/dist/crypto/wallet.js.map +1 -0
  59. package/dist/cu/ni.js.map +1 -1
  60. package/dist/cz/rc.js.map +1 -1
  61. package/dist/de/handelsreg.js.map +1 -1
  62. package/dist/de/idnr.js.map +1 -1
  63. package/dist/de/stnr.js.map +1 -1
  64. package/dist/de/svnr.js.map +1 -1
  65. package/dist/dk/cpr.js.map +1 -1
  66. package/dist/dk/cvr.js.map +1 -1
  67. package/dist/dk/vat.js.map +1 -1
  68. package/dist/do/rnc.js.map +1 -1
  69. package/dist/ec/ruc.js.map +1 -1
  70. package/dist/ee/ik.js.map +1 -1
  71. package/dist/ee/vat.js.map +1 -1
  72. package/dist/eg/tn.js.map +1 -1
  73. package/dist/es/dni.js.map +1 -1
  74. package/dist/es/nie.js.map +1 -1
  75. package/dist/es/nss.js.map +1 -1
  76. package/dist/eu/vat.js.map +1 -1
  77. package/dist/fi/hetu.js.map +1 -1
  78. package/dist/fi/vat.js.map +1 -1
  79. package/dist/fi/ytunnus.js.map +1 -1
  80. package/dist/fr/nif.js.map +1 -1
  81. package/dist/fr/nir.js.map +1 -1
  82. package/dist/fr/tva.js.map +1 -1
  83. package/dist/gb/mod.d.ts +3 -2
  84. package/dist/gb/mod.js +3 -1
  85. package/dist/gb/mod.js.map +1 -1
  86. package/dist/gb/nhs.d.ts +11 -0
  87. package/dist/gb/nhs.js +51 -0
  88. package/dist/gb/nhs.js.map +1 -0
  89. package/dist/gb/nino.js.map +1 -1
  90. package/dist/gb/sedol.js.map +1 -1
  91. package/dist/gb/utr.js.map +1 -1
  92. package/dist/gb/vat.js.map +1 -1
  93. package/dist/ge/pin.js.map +1 -1
  94. package/dist/gh/tin.js.map +1 -1
  95. package/dist/gr/amka.js.map +1 -1
  96. package/dist/gt/nit.js.map +1 -1
  97. package/dist/hk/hkid.js.map +1 -1
  98. package/dist/hu/vat.js.map +1 -1
  99. package/dist/iban.js.map +1 -1
  100. package/dist/id/npwp.js.map +1 -1
  101. package/dist/ie/pps.js.map +1 -1
  102. package/dist/ie/vat.js.map +1 -1
  103. package/dist/il/idnr.js.map +1 -1
  104. package/dist/in/aadhaar.js.map +1 -1
  105. package/dist/in/gstin.js.map +1 -1
  106. package/dist/in/pan.js.map +1 -1
  107. package/dist/index.d.ts +76 -75
  108. package/dist/index.js +2 -1
  109. package/dist/ir/nid.js.map +1 -1
  110. package/dist/is/kennitala.js.map +1 -1
  111. package/dist/isin.js.map +1 -1
  112. package/dist/it/codicefiscale.js.map +1 -1
  113. package/dist/it/iva.js.map +1 -1
  114. package/dist/kr/rrn.js.map +1 -1
  115. package/dist/kw/civil.js.map +1 -1
  116. package/dist/kz/iin.js.map +1 -1
  117. package/dist/lei.js.map +1 -1
  118. package/dist/li/peid.js.map +1 -1
  119. package/dist/lk/nic.js.map +1 -1
  120. package/dist/lt/asmens.js.map +1 -1
  121. package/dist/lt/vat.js.map +1 -1
  122. package/dist/lu/vat.js.map +1 -1
  123. package/dist/lv/vat.js.map +1 -1
  124. package/dist/ma/ice.js.map +1 -1
  125. package/dist/mc/tva.js.map +1 -1
  126. package/dist/mt/vat.js.map +1 -1
  127. package/dist/mu/brn.js.map +1 -1
  128. package/dist/mx/clabe.js.map +1 -1
  129. package/dist/mx/curp.js.map +1 -1
  130. package/dist/mx/rfc.js.map +1 -1
  131. package/dist/my/nric.js.map +1 -1
  132. package/dist/ni/ruc.js.map +1 -1
  133. package/dist/nl/bsn.js.map +1 -1
  134. package/dist/nl/vat.js.map +1 -1
  135. package/dist/no/fodselsnummer.js.map +1 -1
  136. package/dist/no/mva.js.map +1 -1
  137. package/dist/no/orgnr.js.map +1 -1
  138. package/dist/nz/ird.js.map +1 -1
  139. package/dist/pa/ruc.js.map +1 -1
  140. package/dist/patterns.js.map +1 -1
  141. package/dist/pe/ruc.js.map +1 -1
  142. package/dist/pl/pesel.js.map +1 -1
  143. package/dist/pl/regon.js.map +1 -1
  144. package/dist/pt/cc.js.map +1 -1
  145. package/dist/ro/cnp.js.map +1 -1
  146. package/dist/ro/vat.js.map +1 -1
  147. package/dist/rs/pib.js.map +1 -1
  148. package/dist/ru/inn.js.map +1 -1
  149. package/dist/se/personnummer.js.map +1 -1
  150. package/dist/sg/uen.js.map +1 -1
  151. package/dist/si/emso.js.map +1 -1
  152. package/dist/si/vat.js.map +1 -1
  153. package/dist/sk/dic.js.map +1 -1
  154. package/dist/tw/ubn.js.map +1 -1
  155. package/dist/ua/edrpou.js.map +1 -1
  156. package/dist/us/itin.js.map +1 -1
  157. package/dist/us/rtn.js.map +1 -1
  158. package/dist/us/ssn.js.map +1 -1
  159. package/dist/uy/rut.js.map +1 -1
  160. package/dist/ve/rif.js.map +1 -1
  161. package/dist/vn/mst.js.map +1 -1
  162. package/dist/za/idnr.js.map +1 -1
  163. package/package.json +31 -6
  164. package/src/_checksums/keccak.ts +145 -0
  165. package/src/_checksums/sha256.ts +125 -0
  166. package/src/crypto/btcbase58.ts +144 -0
  167. package/src/crypto/btcbech32.ts +210 -0
  168. package/src/crypto/eth.ts +114 -0
  169. package/src/crypto/mod.ts +4 -0
  170. package/src/crypto/wallet.ts +67 -0
  171. package/src/gb/mod.ts +1 -0
  172. package/src/gb/nhs.ts +92 -0
  173. package/src/index.ts +1 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Ethereum address.
3
+ *
4
+ * Validates the canonical 0x-prefixed 20-byte
5
+ * hexadecimal address shape. The compact form is
6
+ * lowercased so checksummed and non-checksummed
7
+ * variants of the same address compare equal.
8
+ */
9
+
10
+ import type {
11
+ GlobalValidator,
12
+ ValidateResult,
13
+ } from "../types";
14
+
15
+ import { keccak256 } from "#checksums/keccak";
16
+ import { clean } from "#util/clean";
17
+ import { err } from "#util/result";
18
+
19
+ const ETH_ADDRESS_LENGTH = 42;
20
+ const HEX_PREFIX_LENGTH = 2;
21
+
22
+ const compact = (value: string): string =>
23
+ clean(value, " ").toLowerCase();
24
+
25
+ const asciiBytes = (value: string): Uint8Array => {
26
+ const bytes = new Uint8Array(value.length);
27
+ for (let i = 0; i < value.length; i += 1) {
28
+ bytes[i] = value.charCodeAt(i);
29
+ }
30
+ return bytes;
31
+ };
32
+
33
+ const hexBytes = (bytes: Uint8Array): string => {
34
+ let result = "";
35
+ for (const byte of bytes) {
36
+ result += byte.toString(16).padStart(2, "0");
37
+ }
38
+ return result;
39
+ };
40
+
41
+ const isMixedCase = (value: string): boolean =>
42
+ /[a-f]/.test(value) && /[A-F]/.test(value);
43
+
44
+ const hasValidEip55Checksum = (
45
+ address: string,
46
+ ): boolean => {
47
+ const body = address.slice(HEX_PREFIX_LENGTH);
48
+ if (!isMixedCase(body)) return true;
49
+
50
+ const lower = body.toLowerCase();
51
+ const hash = hexBytes(keccak256(asciiBytes(lower)));
52
+
53
+ for (let i = 0; i < body.length; i += 1) {
54
+ const ch = body.charAt(i);
55
+ if (/\d/.test(ch)) continue;
56
+
57
+ const nibble = Number.parseInt(hash.charAt(i), 16);
58
+ const expected =
59
+ nibble >= 8 ? ch.toUpperCase() : ch.toLowerCase();
60
+ if (ch !== expected) return false;
61
+ }
62
+
63
+ return true;
64
+ };
65
+
66
+ const validate = (value: string): ValidateResult => {
67
+ const raw = clean(value, " ");
68
+ const v = raw.toLowerCase();
69
+ if (v.length !== ETH_ADDRESS_LENGTH) {
70
+ return err(
71
+ "INVALID_LENGTH",
72
+ "Ethereum address must be 42 characters",
73
+ );
74
+ }
75
+ if (!/^0x[0-9a-f]{40}$/i.test(raw)) {
76
+ return err(
77
+ "INVALID_FORMAT",
78
+ "Ethereum address must be 0x followed by 40 hexadecimal characters",
79
+ );
80
+ }
81
+ if (!hasValidEip55Checksum(raw)) {
82
+ return err(
83
+ "INVALID_CHECKSUM",
84
+ "Ethereum address fails EIP-55 checksum validation",
85
+ );
86
+ }
87
+ return { valid: true, compact: v };
88
+ };
89
+
90
+ const format = (value: string): string => compact(value);
91
+
92
+ const eth: GlobalValidator = {
93
+ scope: "global",
94
+ name: "Ethereum Address",
95
+ localName: "Ethereum Address",
96
+ abbreviation: "ETH",
97
+ aliases: [
98
+ "Ethereum address",
99
+ "EVM address",
100
+ "crypto wallet",
101
+ ] as const,
102
+ candidatePattern: "0x[0-9A-Fa-f]{40}",
103
+ entityType: "any",
104
+ examples: [
105
+ "0xde709f2102306220921060314715629080e2fb77",
106
+ ] as const,
107
+ lengths: [ETH_ADDRESS_LENGTH] as const,
108
+ compact,
109
+ format,
110
+ validate,
111
+ };
112
+
113
+ export default eth;
114
+ export { compact, format, hasValidEip55Checksum, validate };
@@ -0,0 +1,4 @@
1
+ export { default as btcbase58 } from "./btcbase58";
2
+ export { default as btcbech32 } from "./btcbech32";
3
+ export { default as eth } from "./eth";
4
+ export { default as wallet } from "./wallet";
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Cryptocurrency wallet address.
3
+ *
4
+ * Convenience validator for supported wallet address
5
+ * families. Use the specific validators when the
6
+ * network or format must be known.
7
+ */
8
+
9
+ import type {
10
+ GlobalValidator,
11
+ ValidateResult,
12
+ } from "../types";
13
+ import btcBase58 from "./btcbase58";
14
+ import btcBech32 from "./btcbech32";
15
+ import eth from "./eth";
16
+
17
+ const validators = [eth, btcBech32, btcBase58] as const;
18
+
19
+ const compact = (value: string): string => {
20
+ for (const validator of validators) {
21
+ const result = validator.validate(value);
22
+ if (result.valid) return result.compact;
23
+ }
24
+ return value.trim();
25
+ };
26
+
27
+ const validate = (value: string): ValidateResult => {
28
+ for (const validator of validators) {
29
+ const result = validator.validate(value);
30
+ if (result.valid) return result;
31
+ }
32
+ return {
33
+ valid: false,
34
+ error: {
35
+ code: "INVALID_FORMAT",
36
+ message: "Unsupported cryptocurrency wallet address",
37
+ },
38
+ };
39
+ };
40
+
41
+ const format = (value: string): string => compact(value);
42
+
43
+ const wallet: GlobalValidator = {
44
+ scope: "global",
45
+ name: "Cryptocurrency Wallet Address",
46
+ localName: "Cryptocurrency Wallet Address",
47
+ abbreviation: "crypto",
48
+ aliases: [
49
+ "crypto address",
50
+ "wallet address",
51
+ "cryptocurrency wallet",
52
+ ] as const,
53
+ candidatePattern:
54
+ "(?:0x[0-9A-Fa-f]{40}|[13][1-9A-HJ-NP-Za-km-z]{25,34}|bc1[ac-hj-np-z02-9]{11,71}|BC1[AC-HJ-NP-Z02-9]{11,71})",
55
+ entityType: "any",
56
+ examples: [
57
+ "0xde709f2102306220921060314715629080e2fb77",
58
+ "1BoatSLRHtKNngkdXEeobR76b53LETtpyT",
59
+ "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
60
+ ] as const,
61
+ compact,
62
+ format,
63
+ validate,
64
+ };
65
+
66
+ export default wallet;
67
+ export { compact, format, validate };
package/src/gb/mod.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { default as nhs } from "./nhs";
1
2
  export { default as nino } from "./nino";
2
3
  export { default as sedol } from "./sedol";
3
4
  export { default as utr } from "./utr";
package/src/gb/nhs.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * UK NHS Number.
3
+ *
4
+ * Validates the 10-digit NHS number using the
5
+ * modulus 11 checksum used by NHS England.
6
+ */
7
+
8
+ import type {
9
+ CountryValidator,
10
+ ValidateResult,
11
+ } from "../types";
12
+
13
+ import { clean } from "#util/clean";
14
+ import { err } from "#util/result";
15
+ import { isdigits } from "#util/strings";
16
+
17
+ const NHS_NUMBER_LENGTH = 10;
18
+ const CHECK_DIGIT_INDEX = 9;
19
+
20
+ const compact = (value: string): string =>
21
+ clean(value, " ");
22
+
23
+ const calcCheckDigit = (value: string): number | null => {
24
+ if (value.length !== NHS_NUMBER_LENGTH) return null;
25
+ if (!isdigits(value)) return null;
26
+
27
+ let total = 0;
28
+ for (let i = 0; i < CHECK_DIGIT_INDEX; i += 1) {
29
+ total += Number(value.charAt(i)) * (10 - i);
30
+ }
31
+
32
+ const check = 11 - (total % 11);
33
+ if (check === 10) return null;
34
+ return check === 11 ? 0 : check;
35
+ };
36
+
37
+ const validate = (value: string): ValidateResult => {
38
+ const v = compact(value);
39
+ if (v.length !== NHS_NUMBER_LENGTH) {
40
+ return err(
41
+ "INVALID_LENGTH",
42
+ "NHS number must be 10 digits",
43
+ );
44
+ }
45
+ if (!isdigits(v)) {
46
+ return err(
47
+ "INVALID_FORMAT",
48
+ "NHS number must contain only digits",
49
+ );
50
+ }
51
+
52
+ const check = calcCheckDigit(v);
53
+ if (
54
+ check === null ||
55
+ check !== Number(v.charAt(CHECK_DIGIT_INDEX))
56
+ ) {
57
+ return err(
58
+ "INVALID_CHECKSUM",
59
+ "NHS number fails modulus 11 check",
60
+ );
61
+ }
62
+
63
+ return { valid: true, compact: v };
64
+ };
65
+
66
+ const format = (value: string): string => {
67
+ const v = compact(value);
68
+ if (v.length !== NHS_NUMBER_LENGTH) return v;
69
+ return `${v.slice(0, 3)} ${v.slice(3, 6)} ${v.slice(6)}`;
70
+ };
71
+
72
+ const nhs: CountryValidator<"GB"> = {
73
+ scope: "country",
74
+ country: "GB",
75
+ name: "UK NHS Number",
76
+ localName: "NHS number",
77
+ abbreviation: "NHS",
78
+ aliases: [
79
+ "NHS number",
80
+ "National Health Service number",
81
+ ] as const,
82
+ candidatePattern: "\\d{3}\\s?\\d{3}\\s?\\d{4}",
83
+ entityType: "person",
84
+ lengths: [NHS_NUMBER_LENGTH] as const,
85
+ examples: ["4010232137"] as const,
86
+ compact,
87
+ format,
88
+ validate,
89
+ };
90
+
91
+ export default nhs;
92
+ export { calcCheckDigit, compact, format, validate };
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export { default as isin } from "./isin";
23
23
  export { default as lei } from "./lei";
24
24
  export { default as luhn } from "./luhn";
25
25
 
26
+ export * as crypto from "./crypto/mod";
26
27
  export * as ad from "./ad/mod";
27
28
  export * as ae from "./ae/mod";
28
29
  export * as ai from "./ai/mod";