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