@stll/stdnum 2.0.1 → 2.1.1
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.
- package/README.md +11 -9
- package/dist/_checksums/keccak.d.ts +5 -0
- package/dist/_checksums/keccak.js +118 -0
- package/dist/_checksums/keccak.js.map +1 -0
- package/dist/_checksums/mod112.js.map +1 -1
- package/dist/_checksums/sha256.d.ts +5 -0
- package/dist/_checksums/sha256.js +164 -0
- package/dist/_checksums/sha256.js.map +1 -0
- package/dist/_checksums/verhoeff.js.map +1 -1
- package/dist/_util/arabic.js.map +1 -1
- package/dist/_util/clean.d.ts +6 -1
- package/dist/_util/clean.js +7 -2
- package/dist/_util/clean.js.map +1 -1
- package/dist/_util/date.js.map +1 -1
- package/dist/ad/nrt.js.map +1 -1
- package/dist/ae/eid.js.map +1 -1
- package/dist/ai/tin.js.map +1 -1
- package/dist/al/nipt.js.map +1 -1
- package/dist/am/tin.js.map +1 -1
- package/dist/ar/cbu.js.map +1 -1
- package/dist/ar/cuit.js.map +1 -1
- package/dist/ar/dni.js.map +1 -1
- package/dist/at/businessid.js.map +1 -1
- package/dist/at/vnr.js.map +1 -1
- package/dist/au/abn.js.map +1 -1
- package/dist/au/tfn.js.map +1 -1
- package/dist/az/voen.js.map +1 -1
- package/dist/ba/jmbg.js.map +1 -1
- package/dist/be/bis.js.map +1 -1
- package/dist/be/nn.js.map +1 -1
- package/dist/be/vat.js.map +1 -1
- package/dist/bg/egn.js.map +1 -1
- package/dist/bg/vat.js.map +1 -1
- package/dist/bh/cpr.js.map +1 -1
- package/dist/bic.js.map +1 -1
- package/dist/br/cnpj.js.map +1 -1
- package/dist/br/cpf.js.map +1 -1
- package/dist/by/unp.js.map +1 -1
- package/dist/bz/tin.js.map +1 -1
- package/dist/ch/uid.js.map +1 -1
- package/dist/ch/vat.js.map +1 -1
- package/dist/cl/rut.js.map +1 -1
- package/dist/cn/ric.js.map +1 -1
- package/dist/cn/uscc.js.map +1 -1
- package/dist/cr/cpf.js.map +1 -1
- package/dist/crypto/btcbase58.d.ts +11 -0
- package/dist/crypto/btcbase58.js +76 -0
- package/dist/crypto/btcbase58.js.map +1 -0
- package/dist/crypto/btcbech32.d.ts +19 -0
- package/dist/crypto/btcbech32.js +145 -0
- package/dist/crypto/btcbech32.js.map +1 -0
- package/dist/crypto/eth.d.ts +11 -0
- package/dist/crypto/eth.js +63 -0
- package/dist/crypto/eth.js.map +1 -0
- package/dist/crypto/mod.d.ts +12 -0
- package/dist/crypto/mod.js +16 -0
- package/dist/crypto/mod.js.map +1 -0
- package/dist/crypto/wallet.d.ts +10 -0
- package/dist/crypto/wallet.js +55 -0
- package/dist/crypto/wallet.js.map +1 -0
- package/dist/cu/ni.js.map +1 -1
- package/dist/cz/rc.js.map +1 -1
- package/dist/de/handelsreg.js.map +1 -1
- package/dist/de/idnr.js.map +1 -1
- package/dist/de/stnr.js.map +1 -1
- package/dist/de/svnr.js.map +1 -1
- package/dist/dk/cpr.js.map +1 -1
- package/dist/dk/cvr.js.map +1 -1
- package/dist/dk/vat.js.map +1 -1
- package/dist/do/rnc.js.map +1 -1
- package/dist/ec/ruc.js.map +1 -1
- package/dist/ee/ik.js.map +1 -1
- package/dist/ee/vat.js.map +1 -1
- package/dist/eg/tn.js.map +1 -1
- package/dist/es/dni.js.map +1 -1
- package/dist/es/nie.js.map +1 -1
- package/dist/es/nss.js.map +1 -1
- package/dist/eu/vat.js.map +1 -1
- package/dist/fi/hetu.js.map +1 -1
- package/dist/fi/vat.js.map +1 -1
- package/dist/fi/ytunnus.js.map +1 -1
- package/dist/fr/nif.js.map +1 -1
- package/dist/fr/nir.js.map +1 -1
- package/dist/fr/tva.js.map +1 -1
- package/dist/gb/mod.d.ts +3 -2
- package/dist/gb/mod.js +3 -1
- package/dist/gb/mod.js.map +1 -1
- package/dist/gb/nhs.d.ts +11 -0
- package/dist/gb/nhs.js +51 -0
- package/dist/gb/nhs.js.map +1 -0
- package/dist/gb/nino.js.map +1 -1
- package/dist/gb/sedol.js.map +1 -1
- package/dist/gb/utr.js.map +1 -1
- package/dist/gb/vat.js.map +1 -1
- package/dist/ge/pin.js.map +1 -1
- package/dist/gh/tin.js.map +1 -1
- package/dist/gr/amka.js.map +1 -1
- package/dist/gt/nit.js.map +1 -1
- package/dist/hk/hkid.js.map +1 -1
- package/dist/hu/vat.js.map +1 -1
- package/dist/iban.js.map +1 -1
- package/dist/id/npwp.js.map +1 -1
- package/dist/ie/pps.js.map +1 -1
- package/dist/ie/vat.js.map +1 -1
- package/dist/il/idnr.js.map +1 -1
- package/dist/in/aadhaar.js.map +1 -1
- package/dist/in/gstin.js.map +1 -1
- package/dist/in/pan.js.map +1 -1
- package/dist/index.d.ts +76 -75
- package/dist/index.js +2 -1
- package/dist/ir/nid.js.map +1 -1
- package/dist/is/kennitala.js.map +1 -1
- package/dist/isin.js.map +1 -1
- package/dist/it/codicefiscale.js.map +1 -1
- package/dist/it/iva.js.map +1 -1
- package/dist/kr/rrn.js.map +1 -1
- package/dist/kw/civil.js.map +1 -1
- package/dist/kz/iin.js.map +1 -1
- package/dist/lei.js.map +1 -1
- package/dist/li/peid.js.map +1 -1
- package/dist/lk/nic.js.map +1 -1
- package/dist/lt/asmens.js.map +1 -1
- package/dist/lt/vat.js.map +1 -1
- package/dist/lu/vat.js.map +1 -1
- package/dist/lv/vat.js.map +1 -1
- package/dist/ma/ice.js.map +1 -1
- package/dist/mc/tva.js.map +1 -1
- package/dist/mt/vat.js.map +1 -1
- package/dist/mu/brn.js.map +1 -1
- package/dist/mx/clabe.js.map +1 -1
- package/dist/mx/curp.js.map +1 -1
- package/dist/mx/rfc.js.map +1 -1
- package/dist/my/nric.js.map +1 -1
- package/dist/ni/ruc.js.map +1 -1
- package/dist/nl/bsn.js.map +1 -1
- package/dist/nl/vat.js.map +1 -1
- package/dist/no/fodselsnummer.js.map +1 -1
- package/dist/no/mva.js.map +1 -1
- package/dist/no/orgnr.js.map +1 -1
- package/dist/nz/ird.js.map +1 -1
- package/dist/pa/ruc.js.map +1 -1
- package/dist/patterns.js.map +1 -1
- package/dist/pe/ruc.js.map +1 -1
- package/dist/pl/pesel.js.map +1 -1
- package/dist/pl/regon.js.map +1 -1
- package/dist/pt/cc.js.map +1 -1
- package/dist/ro/cnp.js.map +1 -1
- package/dist/ro/vat.js.map +1 -1
- package/dist/rs/pib.js.map +1 -1
- package/dist/ru/inn.js.map +1 -1
- package/dist/se/personnummer.js.map +1 -1
- package/dist/sg/uen.js.map +1 -1
- package/dist/si/emso.js.map +1 -1
- package/dist/si/vat.js.map +1 -1
- package/dist/sk/dic.js.map +1 -1
- package/dist/tw/ubn.js.map +1 -1
- package/dist/ua/edrpou.js.map +1 -1
- package/dist/us/itin.js.map +1 -1
- package/dist/us/rtn.js.map +1 -1
- package/dist/us/ssn.js.map +1 -1
- package/dist/uy/rut.js.map +1 -1
- package/dist/ve/rif.js.map +1 -1
- package/dist/vn/mst.js.map +1 -1
- package/dist/za/idnr.js.map +1 -1
- package/package.json +31 -6
- package/src/_checksums/keccak.ts +145 -0
- package/src/_checksums/sha256.ts +125 -0
- package/src/_util/clean.ts +7 -2
- package/src/crypto/btcbase58.ts +144 -0
- package/src/crypto/btcbech32.ts +210 -0
- package/src/crypto/eth.ts +114 -0
- package/src/crypto/mod.ts +4 -0
- package/src/crypto/wallet.ts +67 -0
- package/src/gb/mod.ts +1 -0
- package/src/gb/nhs.ts +92 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bitcoin Bech32/Bech32m address.
|
|
3
|
+
*
|
|
4
|
+
* Validates mainnet SegWit addresses with the
|
|
5
|
+
* Bech32 checksum for v0 and Bech32m for v1+.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
GlobalValidator,
|
|
10
|
+
ValidateResult,
|
|
11
|
+
} from "../types";
|
|
12
|
+
|
|
13
|
+
import { clean } from "#util/clean";
|
|
14
|
+
import { err } from "#util/result";
|
|
15
|
+
|
|
16
|
+
const CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
17
|
+
const GENERATORS = [
|
|
18
|
+
0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd,
|
|
19
|
+
0x2a1462b3,
|
|
20
|
+
] as const;
|
|
21
|
+
const BECH32_CONST = 1;
|
|
22
|
+
const BECH32M_CONST = 0x2bc830a3;
|
|
23
|
+
const CHECKSUM_LENGTH = 6;
|
|
24
|
+
const MIN_DATA_LENGTH = 11;
|
|
25
|
+
const MAX_DATA_LENGTH = 71;
|
|
26
|
+
const BC_HRP_EXPANSION = [3, 3, 0, 2, 3] as const;
|
|
27
|
+
|
|
28
|
+
const compact = (value: string): string =>
|
|
29
|
+
clean(value, " ").toLowerCase();
|
|
30
|
+
|
|
31
|
+
const polymod = (values: readonly number[]): number => {
|
|
32
|
+
let chk = 1;
|
|
33
|
+
for (const value of values) {
|
|
34
|
+
const top = chk >>> 25;
|
|
35
|
+
chk = (((chk & 0x1ffffff) << 5) ^ value) >>> 0;
|
|
36
|
+
for (let i = 0; i < GENERATORS.length; i += 1) {
|
|
37
|
+
const generator = GENERATORS[i] ?? 0;
|
|
38
|
+
if (((top >>> i) & 1) === 1) {
|
|
39
|
+
chk = (chk ^ generator) >>> 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return chk >>> 0;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const dataValues = (data: string): number[] | null => {
|
|
47
|
+
const values: number[] = [];
|
|
48
|
+
for (const ch of data) {
|
|
49
|
+
const value = CHARSET.indexOf(ch);
|
|
50
|
+
if (value === -1) return null;
|
|
51
|
+
values.push(value);
|
|
52
|
+
}
|
|
53
|
+
return values;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const convertBits = (
|
|
57
|
+
values: readonly number[],
|
|
58
|
+
fromBits: number,
|
|
59
|
+
toBits: number,
|
|
60
|
+
): number[] | null => {
|
|
61
|
+
let accumulator = 0;
|
|
62
|
+
let bits = 0;
|
|
63
|
+
const maxValue = (1 << toBits) - 1;
|
|
64
|
+
const result: number[] = [];
|
|
65
|
+
|
|
66
|
+
for (const value of values) {
|
|
67
|
+
if (value < 0 || value >>> fromBits !== 0) return null;
|
|
68
|
+
accumulator = (accumulator << fromBits) | value;
|
|
69
|
+
bits += fromBits;
|
|
70
|
+
while (bits >= toBits) {
|
|
71
|
+
bits -= toBits;
|
|
72
|
+
result.push((accumulator >>> bits) & maxValue);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (bits >= fromBits) return null;
|
|
77
|
+
if (((accumulator << (toBits - bits)) & maxValue) !== 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type Bech32Validation =
|
|
85
|
+
| { valid: true }
|
|
86
|
+
| {
|
|
87
|
+
valid: false;
|
|
88
|
+
code: "format" | "checksum" | "component";
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const validateBech32 = (
|
|
92
|
+
rawValue: string,
|
|
93
|
+
): Bech32Validation => {
|
|
94
|
+
const hasLower = /[a-z]/.test(rawValue);
|
|
95
|
+
const hasUpper = /[A-Z]/.test(rawValue);
|
|
96
|
+
if (hasLower && hasUpper)
|
|
97
|
+
return { valid: false, code: "format" };
|
|
98
|
+
|
|
99
|
+
const v = rawValue.toLowerCase();
|
|
100
|
+
if (!v.startsWith("bc1"))
|
|
101
|
+
return { valid: false, code: "component" };
|
|
102
|
+
|
|
103
|
+
const data = v.slice(3);
|
|
104
|
+
if (
|
|
105
|
+
data.length < MIN_DATA_LENGTH ||
|
|
106
|
+
data.length > MAX_DATA_LENGTH
|
|
107
|
+
) {
|
|
108
|
+
return { valid: false, code: "format" };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const values = dataValues(data);
|
|
112
|
+
if (values === null)
|
|
113
|
+
return { valid: false, code: "format" };
|
|
114
|
+
|
|
115
|
+
const expanded: number[] = Array.from(BC_HRP_EXPANSION);
|
|
116
|
+
for (const value of values) {
|
|
117
|
+
expanded.push(value);
|
|
118
|
+
}
|
|
119
|
+
const check = polymod(expanded);
|
|
120
|
+
|
|
121
|
+
const version = values[0];
|
|
122
|
+
if (version === undefined || version > 16) {
|
|
123
|
+
return { valid: false, code: "component" };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (version === 0 && check !== BECH32_CONST) {
|
|
127
|
+
return { valid: false, code: "checksum" };
|
|
128
|
+
}
|
|
129
|
+
if (version > 0 && check !== BECH32M_CONST) {
|
|
130
|
+
return { valid: false, code: "checksum" };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const programValues = values.slice(1, -CHECKSUM_LENGTH);
|
|
134
|
+
const program = convertBits(programValues, 5, 8);
|
|
135
|
+
if (
|
|
136
|
+
program === null ||
|
|
137
|
+
program.length < 2 ||
|
|
138
|
+
program.length > 40
|
|
139
|
+
) {
|
|
140
|
+
return { valid: false, code: "component" };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
version === 0 &&
|
|
145
|
+
program.length !== 20 &&
|
|
146
|
+
program.length !== 32
|
|
147
|
+
) {
|
|
148
|
+
return { valid: false, code: "component" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { valid: true };
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const validate = (value: string): ValidateResult => {
|
|
155
|
+
const v = compact(value);
|
|
156
|
+
const result = validateBech32(clean(value, " "));
|
|
157
|
+
if (result.valid) return { valid: true, compact: v };
|
|
158
|
+
|
|
159
|
+
if (result.code === "component") {
|
|
160
|
+
return err(
|
|
161
|
+
"INVALID_COMPONENT",
|
|
162
|
+
"Bitcoin Bech32 address has an unsupported component",
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (result.code === "checksum") {
|
|
166
|
+
return err(
|
|
167
|
+
"INVALID_CHECKSUM",
|
|
168
|
+
"Bitcoin Bech32 address fails checksum validation",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return err(
|
|
172
|
+
"INVALID_FORMAT",
|
|
173
|
+
"Bitcoin Bech32 address has an invalid format",
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const format = (value: string): string => compact(value);
|
|
178
|
+
|
|
179
|
+
const btcBech32: GlobalValidator = {
|
|
180
|
+
scope: "global",
|
|
181
|
+
name: "Bitcoin Bech32 Address",
|
|
182
|
+
localName: "Bitcoin Bech32 Address",
|
|
183
|
+
abbreviation: "BTC",
|
|
184
|
+
aliases: [
|
|
185
|
+
"Bitcoin address",
|
|
186
|
+
"Bitcoin SegWit address",
|
|
187
|
+
"Bech32 address",
|
|
188
|
+
"Bech32m address",
|
|
189
|
+
] as const,
|
|
190
|
+
candidatePattern:
|
|
191
|
+
"(?:bc1[ac-hj-np-z02-9]{11,71}|BC1[AC-HJ-NP-Z02-9]{11,71})",
|
|
192
|
+
entityType: "any",
|
|
193
|
+
examples: [
|
|
194
|
+
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
|
|
195
|
+
"bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3",
|
|
196
|
+
] as const,
|
|
197
|
+
compact,
|
|
198
|
+
format,
|
|
199
|
+
validate,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export default btcBech32;
|
|
203
|
+
export {
|
|
204
|
+
compact,
|
|
205
|
+
convertBits,
|
|
206
|
+
format,
|
|
207
|
+
polymod,
|
|
208
|
+
validate,
|
|
209
|
+
validateBech32,
|
|
210
|
+
};
|
|
@@ -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,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
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";
|