1 | 'use strict' |
||
2 | 1 | let BigInteger = require('bigi') |
|
3 | 1 | let ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' |
|
4 | |||
5 | // pre-compute lookup table |
||
6 | 1 | let SEPARATOR = ':' |
|
7 | 1 | let CSLEN = 8 |
|
8 | 1 | let ALPHABET_MAP = {} |
|
9 | 1 | for (let z = 0; z < ALPHABET.length; z++) { |
|
10 | 32 | let x = ALPHABET.charAt(z) |
|
11 | 32 | if (ALPHABET_MAP[x] !== undefined) { |
|
12 | throw new TypeError(x + ' is ambiguous') |
||
13 | } |
||
14 | 32 | ALPHABET_MAP[x] = z |
|
15 | } |
||
16 | |||
17 | function polymodStep (pre) { |
||
18 | 2432 | let b = pre.shiftRight(35) |
|
19 | 2432 | let mask = BigInteger.fromHex('07ffffffff') |
|
20 | |||
21 | 2432 | let v = pre.and(mask).shiftLeft(new BigInteger('5')) |
|
22 | |||
23 | 2432 | if (b.and(new BigInteger('1')).intValue() > 0) { |
|
24 | 1080 | v = v.xor(BigInteger.fromHex('98f2bc8e61')) |
|
25 | } |
||
26 | 2432 | if (b.and(new BigInteger('2')).intValue()) { |
|
27 | 1033 | v = v.xor(BigInteger.fromHex('79b76d99e2')) |
|
28 | } |
||
29 | 2432 | if (b.and(new BigInteger('4')).intValue()) { |
|
30 | 1030 | v = v.xor(BigInteger.fromHex('f33e5fb3c4')) |
|
31 | } |
||
32 | 2432 | if (b.and(new BigInteger('8')).intValue()) { |
|
33 | 1141 | v = v.xor(BigInteger.fromHex('ae2eabe2a8')) |
|
34 | } |
||
35 | 2432 | if (b.and(new BigInteger('16')).intValue()) { |
|
36 | 1123 | v = v.xor(BigInteger.fromHex('1e4f43e470')) |
|
37 | } |
||
38 | |||
39 | 2432 | return v |
|
40 | } |
||
41 | |||
42 | function prefixChk (prefix) { |
||
43 | 48 | let chk = new BigInteger('1') |
|
44 | 48 | for (let i = 0; i < prefix.length; ++i) { |
|
45 | 483 | let c = prefix.charCodeAt(i) |
|
46 | |||
47 | 483 | let mixwith = new BigInteger('' + (c & 0x1f)) |
|
48 | 483 | chk = polymodStep(chk).xor(mixwith) |
|
49 | } |
||
50 | |||
51 | 48 | chk = polymodStep(chk) |
|
52 | 48 | return chk |
|
53 | } |
||
54 | |||
55 | function encode (prefix, words) { |
||
56 | // too long? |
||
57 | 18 | if ((prefix.length + CSLEN + 1 + words.length) > 90) { |
|
58 | 1 | throw new TypeError('Exceeds Base32 maximum length') |
|
59 | } |
||
60 | |||
61 | 17 | prefix = prefix.toLowerCase() |
|
62 | |||
63 | // determine chk mod |
||
64 | 17 | let chk = prefixChk(prefix) |
|
65 | 17 | let result = prefix + SEPARATOR |
|
66 | 17 | for (let i = 0; i < words.length; ++i) { |
|
67 | 547 | let x = words[i] |
|
68 | 547 | if ((x >>> 5) !== 0) { |
|
69 | 1 | throw new Error('Non 5-bit word') |
|
70 | } |
||
71 | |||
72 | 546 | chk = polymodStep(chk).xor(new BigInteger('' + x)) |
|
73 | 546 | result += ALPHABET.charAt(x) |
|
74 | } |
||
75 | |||
76 | 16 | for (let i = 0; i < CSLEN; ++i) { |
|
77 | 128 | chk = polymodStep(chk) |
|
78 | } |
||
79 | 16 | chk = chk.xor(new BigInteger('1')) |
|
80 | 16 | for (let i = 0; i < CSLEN; ++i) { |
|
81 | 128 | let pos = 5 * (CSLEN - 1 - i) |
|
82 | 128 | let v2 = chk.shiftRight(new BigInteger('' + pos)).and(BigInteger.fromHex('1f')) |
|
83 | 128 | result += ALPHABET.charAt(v2.toString(10)) |
|
84 | } |
||
85 | |||
86 | 16 | return result |
|
87 | } |
||
88 | |||
89 | function decode (str) { |
||
90 | 37 | if (str.length < 8) { |
|
91 | 1 | throw new TypeError(str + ' too short') |
|
92 | } |
||
93 | 36 | if (str.length > 90) { |
|
94 | 1 | throw new TypeError(str + ' too long') |
|
95 | } |
||
96 | |||
97 | // don't allow mixed case |
||
98 | 35 | let lowered = str.toLowerCase() |
|
99 | 35 | let uppered = str.toUpperCase() |
|
100 | 35 | if (str !== lowered && str !== uppered) { |
|
101 | 1 | throw new Error('Mixed-case string ' + str) |
|
102 | } |
||
103 | |||
104 | 34 | str = lowered |
|
105 | |||
106 | 34 | let split = str.lastIndexOf(SEPARATOR) |
|
107 | 34 | if (split === -1) { |
|
108 | 1 | throw new Error('No separator character for ' + str) |
|
109 | } |
||
110 | |||
111 | 33 | if (split === 0) { |
|
112 | 1 | throw new Error('Missing prefix for ' + str) |
|
113 | } |
||
114 | |||
115 | 32 | let prefix = str.slice(0, split) |
|
116 | 32 | let wordChars = str.slice(split + 1) |
|
117 | 32 | if (wordChars.length < 6) { |
|
118 | 1 | throw new Error('Data too short') |
|
119 | } |
||
120 | |||
121 | 31 | let chk = prefixChk(prefix) |
|
122 | 31 | let words = [] |
|
123 | 31 | for (let i = 0; i < wordChars.length; ++i) { |
|
124 | 1228 | let c = wordChars.charAt(i) |
|
125 | 1228 | let v = ALPHABET_MAP[c] |
|
126 | 1228 | if (v === undefined) { |
|
127 | 1 | throw new Error('Unknown character ' + c) |
|
128 | } |
||
129 | |||
130 | 1227 | chk = polymodStep(chk).xor(new BigInteger('' + v)) |
|
131 | // not in the checksum? |
||
132 | 1227 | if (i + CSLEN >= wordChars.length) { |
|
133 | 240 | continue |
|
134 | } |
||
135 | 987 | words.push(v) |
|
136 | } |
||
137 | |||
138 | 30 | if (chk.toString(10) !== '1') { |
|
139 | 9 | throw new Error('Invalid checksum for ' + str) |
|
140 | } |
||
141 | |||
142 | 21 | return { prefix, words } |
|
143 | } |
||
144 | |||
145 | function convert (data, inBits, outBits, pad) { |
||
146 | 39 | let value = 0 |
|
147 | 39 | let bits = 0 |
|
148 | 39 | let maxV = (1 << outBits) - 1 |
|
149 | |||
150 | 39 | let result = [] |
|
151 | 39 | for (let i = 0; i < data.length; ++i) { |
|
152 | 1058 | value = (value << inBits) | data[i] |
|
153 | 1058 | bits += inBits |
|
154 | |||
155 | 1058 | while (bits >= outBits) { |
|
156 | 973 | bits -= outBits |
|
157 | 973 | result.push((value >>> bits) & maxV) |
|
158 | } |
||
159 | } |
||
160 | |||
161 | 39 | if (pad) { |
|
162 | 16 | if (bits > 0) { |
|
163 | 16 | result.push((value << (outBits - bits)) & maxV) |
|
164 | } |
||
165 | } else { |
||
166 | 23 | if (bits >= inBits) { |
|
167 | 1 | throw new Error('Excess padding') |
|
168 | } |
||
169 | 22 | if ((value << (outBits - bits)) & maxV) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
170 | 1 | throw new Error('Non-zero padding') |
|
171 | } |
||
172 | } |
||
173 | |||
174 | 37 | return result |
|
175 | } |
||
176 | |||
177 | function toWords (bytes) { |
||
178 | 16 | return convert(bytes, 8, 5, true) |
|
179 | } |
||
180 | |||
181 | function fromWords (words) { |
||
182 | 23 | return convert(words, 5, 8, false) |
|
183 | } |
||
184 | |||
185 | module.exports = { decode, encode, toWords, fromWords } |
||
186 |