@exodus/bytes 1.0.0-rc.4 → 1.0.0-rc.6
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/assert.js +0 -4
- package/base32.js +12 -5
- package/base58.js +212 -0
- package/base58check.js +68 -0
- package/base64.js +72 -40
- package/fallback/base32.js +70 -35
- package/fallback/base64.js +56 -26
- package/fallback/hex.js +137 -49
- package/fallback/latin1.js +89 -0
- package/fallback/utf8.js +77 -112
- package/hex.js +11 -1
- package/package.json +29 -6
- package/utf8.js +20 -49
package/assert.js
CHANGED
package/base32.js
CHANGED
|
@@ -12,14 +12,21 @@ export const toBase32 = (arr, { padding = false } = {}) => js.toBase32(arr, fals
|
|
|
12
12
|
export const toBase32hex = (arr, { padding = false } = {}) => js.toBase32(arr, true, padding)
|
|
13
13
|
|
|
14
14
|
// By default, valid padding is accepted but not required
|
|
15
|
-
export
|
|
16
|
-
fromBase32common(str, false,
|
|
17
|
-
|
|
18
|
-
fromBase32common(str,
|
|
15
|
+
export function fromBase32(str, options) {
|
|
16
|
+
if (!options) return fromBase32common(str, false, 'both', 'uint8', null)
|
|
17
|
+
const { format = 'uint8', padding = 'both', ...rest } = options
|
|
18
|
+
return fromBase32common(str, false, padding, format, rest)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function fromBase32hex(str, options) {
|
|
22
|
+
if (!options) return fromBase32common(str, true, 'both', 'uint8', null)
|
|
23
|
+
const { format = 'uint8', padding = 'both', ...rest } = options
|
|
24
|
+
return fromBase32common(str, true, padding, format, rest)
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
function fromBase32common(str, isBase32Hex, padding, format, rest) {
|
|
21
28
|
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
22
|
-
assertEmptyRest(rest)
|
|
29
|
+
if (rest !== null) assertEmptyRest(rest)
|
|
23
30
|
|
|
24
31
|
if (padding === true) {
|
|
25
32
|
if (str.length % 8 !== 0) throw new SyntaxError(E_PADDING)
|
package/base58.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { typedView } from './array.js'
|
|
2
|
+
import { assertUint8 } from './assert.js'
|
|
3
|
+
import { nativeDecoder, nativeEncoder } from './fallback/_utils.js'
|
|
4
|
+
import { encodeAscii } from './fallback/latin1.js'
|
|
5
|
+
|
|
6
|
+
const alphabet = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
|
|
7
|
+
const codes = new Uint8Array(alphabet.map((x) => x.charCodeAt(0)))
|
|
8
|
+
const ZERO = alphabet[0]
|
|
9
|
+
const zeroC = codes[0]
|
|
10
|
+
|
|
11
|
+
const _0n = BigInt(0)
|
|
12
|
+
const _1n = BigInt(1)
|
|
13
|
+
const _8n = BigInt(8)
|
|
14
|
+
const _32n = BigInt(32)
|
|
15
|
+
const _58n = BigInt(58)
|
|
16
|
+
const _0xffffffffn = BigInt(0xff_ff_ff_ff)
|
|
17
|
+
|
|
18
|
+
let table // 15 * 82, diagonal, <1kb
|
|
19
|
+
let fromMap
|
|
20
|
+
|
|
21
|
+
const E_CHAR = 'Invalid character in base58 input'
|
|
22
|
+
|
|
23
|
+
const shouldUseBigIntFrom = Boolean(globalThis.HermesInternal) // faster only on Hermes, numbers path beats it on normal engines
|
|
24
|
+
|
|
25
|
+
export function toBase58(arr) {
|
|
26
|
+
assertUint8(arr)
|
|
27
|
+
const length = arr.length
|
|
28
|
+
if (length === 0) return ''
|
|
29
|
+
|
|
30
|
+
let zeros = 0
|
|
31
|
+
while (zeros < length && arr[zeros] === 0) zeros++
|
|
32
|
+
|
|
33
|
+
if (length > 60) {
|
|
34
|
+
// Slow path. Can be optimized ~10%, but the main factor is /58n division anyway, so doesn't matter much
|
|
35
|
+
let x = _0n
|
|
36
|
+
for (let i = 0; i < arr.length; i++) x = (x << _8n) | BigInt(arr[i])
|
|
37
|
+
|
|
38
|
+
let out = ''
|
|
39
|
+
while (x) {
|
|
40
|
+
const d = x / _58n
|
|
41
|
+
out = alphabet[Number(x - _58n * d)] + out
|
|
42
|
+
x = d
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return ZERO.repeat(zeros) + out
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// We run fast mode operations only on short (<=60 bytes) inputs, via precomputation table
|
|
49
|
+
if (!table) {
|
|
50
|
+
table = []
|
|
51
|
+
let x = _1n
|
|
52
|
+
for (let i = 0; i < 15; i++) {
|
|
53
|
+
// Convert x to base 58 digits
|
|
54
|
+
const in58 = []
|
|
55
|
+
let y = x
|
|
56
|
+
while (y) {
|
|
57
|
+
const d = y / _58n
|
|
58
|
+
in58.push(Number(y - _58n * d))
|
|
59
|
+
y = d
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
table.push(new Uint8Array(in58))
|
|
63
|
+
x <<= _32n
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const res = []
|
|
68
|
+
{
|
|
69
|
+
let j = 0
|
|
70
|
+
// We group each 4 bytes into 32-bit chunks
|
|
71
|
+
// Not using u32arr to not deal with remainder + BE/LE differences
|
|
72
|
+
for (let i = length - 1; i >= 0; i -= 4) {
|
|
73
|
+
let c
|
|
74
|
+
if (i > 2) {
|
|
75
|
+
c = (arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16) | (arr[i - 3] << 24)) >>> 0
|
|
76
|
+
} else if (i > 1) {
|
|
77
|
+
c = arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16)
|
|
78
|
+
} else {
|
|
79
|
+
c = i === 1 ? arr[i] | (arr[i - 1] << 8) : arr[i]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const row = table[j++]
|
|
83
|
+
if (c === 0) continue
|
|
84
|
+
const olen = res.length
|
|
85
|
+
const nlen = row.length
|
|
86
|
+
let k = 0
|
|
87
|
+
for (; k < olen; k++) res[k] += c * row[k]
|
|
88
|
+
while (k < nlen) res.push(c * row[k++])
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// We can now do a single scan over regular numbers under MAX_SAFE_INTEGER
|
|
93
|
+
// Note: can't use int32 operations on them, as they are outside of 2**32 range
|
|
94
|
+
// This is faster though
|
|
95
|
+
{
|
|
96
|
+
let carry = 0
|
|
97
|
+
let i = 0
|
|
98
|
+
while (i < res.length) {
|
|
99
|
+
const c = res[i] + carry
|
|
100
|
+
carry = Math.floor(c / 58)
|
|
101
|
+
res[i++] = c - carry * 58
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
while (carry) {
|
|
105
|
+
const c = carry
|
|
106
|
+
carry = Math.floor(c / 58)
|
|
107
|
+
res[i++] = c - carry * 58
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (nativeDecoder) {
|
|
112
|
+
const oa = new Uint8Array(res.length)
|
|
113
|
+
let j = 0
|
|
114
|
+
for (let i = res.length - 1; i >= 0; i--) oa[j++] = codes[res[i]]
|
|
115
|
+
return ZERO.repeat(zeros) + nativeDecoder.decode(oa)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let out = ''
|
|
119
|
+
for (let i = res.length - 1; i >= 0; i--) out += alphabet[res[i]]
|
|
120
|
+
return ZERO.repeat(zeros) + out
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// TODO: test on 'z'.repeat(from 1 to smth)
|
|
124
|
+
export function fromBase58(str, format = 'uint8') {
|
|
125
|
+
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
126
|
+
const length = str.length
|
|
127
|
+
if (length === 0) return typedView(new Uint8Array(), format)
|
|
128
|
+
|
|
129
|
+
let zeros = 0
|
|
130
|
+
while (zeros < length && str.charCodeAt(zeros) === zeroC) zeros++
|
|
131
|
+
|
|
132
|
+
if (!fromMap) {
|
|
133
|
+
fromMap = new Int8Array(256).fill(-1)
|
|
134
|
+
for (let i = 0; i < 58; i++) fromMap[alphabet[i].charCodeAt(0)] = i
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const size = zeros + (((length - zeros) * 3 + 1) >> 2) // 3/4 rounded up, larger than ~0.73 coef to fit everything
|
|
138
|
+
const res = new Uint8Array(size)
|
|
139
|
+
let at = size // where is the first significant byte written
|
|
140
|
+
|
|
141
|
+
if (shouldUseBigIntFrom) {
|
|
142
|
+
let x = _0n
|
|
143
|
+
|
|
144
|
+
// nativeEncoder gives a benefit here
|
|
145
|
+
if (nativeEncoder) {
|
|
146
|
+
const codes = encodeAscii(str, E_CHAR)
|
|
147
|
+
for (let i = zeros; i < length; i++) {
|
|
148
|
+
const c = fromMap[codes[i]]
|
|
149
|
+
if (c < 0) throw new SyntaxError(E_CHAR)
|
|
150
|
+
x = x * _58n + BigInt(c)
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
for (let i = zeros; i < length; i++) {
|
|
154
|
+
const charCode = str.charCodeAt(i)
|
|
155
|
+
const c = fromMap[charCode]
|
|
156
|
+
if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
|
|
157
|
+
x = x * _58n + BigInt(c)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
while (x) {
|
|
162
|
+
let y = Number(x & _0xffffffffn)
|
|
163
|
+
x >>= 32n
|
|
164
|
+
res[--at] = y & 0xff
|
|
165
|
+
y >>>= 8
|
|
166
|
+
if (!x && !y) break
|
|
167
|
+
res[--at] = y & 0xff
|
|
168
|
+
y >>>= 8
|
|
169
|
+
if (!x && !y) break
|
|
170
|
+
res[--at] = y & 0xff
|
|
171
|
+
y >>>= 8
|
|
172
|
+
if (!x && !y) break
|
|
173
|
+
res[--at] = y & 0xff
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
for (let i = zeros; i < length; i++) {
|
|
177
|
+
const charCode = str.charCodeAt(i)
|
|
178
|
+
let c = fromMap[charCode]
|
|
179
|
+
if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
|
|
180
|
+
|
|
181
|
+
let k = size - 1
|
|
182
|
+
for (;;) {
|
|
183
|
+
if (c === 0 && k < at) break
|
|
184
|
+
c += 58 * res[k]
|
|
185
|
+
res[k] = c & 0xff
|
|
186
|
+
c >>>= 8
|
|
187
|
+
k--
|
|
188
|
+
// unroll a bit
|
|
189
|
+
if (c === 0 && k < at) break
|
|
190
|
+
c += 58 * res[k]
|
|
191
|
+
res[k] = c & 0xff
|
|
192
|
+
c >>>= 8
|
|
193
|
+
k--
|
|
194
|
+
if (c === 0 && k < at) break
|
|
195
|
+
c += 58 * res[k]
|
|
196
|
+
res[k] = c & 0xff
|
|
197
|
+
c >>>= 8
|
|
198
|
+
k--
|
|
199
|
+
if (c === 0 && k < at) break
|
|
200
|
+
c += 58 * res[k]
|
|
201
|
+
res[k] = c & 0xff
|
|
202
|
+
c >>>= 8
|
|
203
|
+
k--
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
at = k + 1
|
|
207
|
+
if (c !== 0 || at < zeros) throw new Error('Unexpected') // unreachable
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return typedView(res.slice(at - zeros), format) // slice is faster for small sizes than subarray
|
|
212
|
+
}
|
package/base58check.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { typedView } from './array.js'
|
|
2
|
+
import { assertUint8 } from './assert.js'
|
|
3
|
+
import { toBase58, fromBase58 } from './base58.js'
|
|
4
|
+
import { hashSync } from '@exodus/crypto/hash'
|
|
5
|
+
|
|
6
|
+
// Note: while API is async, we use hashSync for now until we improve webcrypto perf for hash256
|
|
7
|
+
// Inputs to base58 are typically very small, and that makes a difference
|
|
8
|
+
|
|
9
|
+
const E_CHECKSUM = 'Invalid checksum'
|
|
10
|
+
|
|
11
|
+
// checksum length is 4, i.e. only the first 4 bytes of the hash are used
|
|
12
|
+
|
|
13
|
+
function encodeWithChecksum(arr, checksum) {
|
|
14
|
+
// arr type in already validated in input
|
|
15
|
+
const res = new Uint8Array(arr.length + 4)
|
|
16
|
+
res.set(arr, 0)
|
|
17
|
+
res.set(checksum.subarray(0, 4), arr.length)
|
|
18
|
+
return toBase58(res)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function decodeWithChecksum(str) {
|
|
22
|
+
const arr = fromBase58(str) // checks input
|
|
23
|
+
const payloadSize = arr.length - 4
|
|
24
|
+
if (payloadSize < 0) throw new Error(E_CHECKSUM)
|
|
25
|
+
return [arr.subarray(0, payloadSize), arr.subarray(payloadSize)]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertChecksum(c, r) {
|
|
29
|
+
if ((c[0] ^ r[0]) | (c[1] ^ r[1]) | (c[2] ^ r[2]) | (c[3] ^ r[3])) throw new Error(E_CHECKSUM)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const makeBase58check = (hashAlgo, hashAlgoSync) => {
|
|
33
|
+
const apis = {
|
|
34
|
+
async encode(arr) {
|
|
35
|
+
assertUint8(arr)
|
|
36
|
+
return encodeWithChecksum(arr, await hashAlgo(arr))
|
|
37
|
+
},
|
|
38
|
+
async decode(str, format = 'uint8') {
|
|
39
|
+
const [payload, checksum] = decodeWithChecksum(str)
|
|
40
|
+
assertChecksum(checksum, await hashAlgo(payload))
|
|
41
|
+
return typedView(payload, format)
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
if (!hashAlgoSync) return apis
|
|
45
|
+
return {
|
|
46
|
+
...apis,
|
|
47
|
+
encodeSync(arr) {
|
|
48
|
+
assertUint8(arr)
|
|
49
|
+
return encodeWithChecksum(arr, hashAlgoSync(arr))
|
|
50
|
+
},
|
|
51
|
+
decodeSync(str, format = 'uint8') {
|
|
52
|
+
const [payload, checksum] = decodeWithChecksum(str)
|
|
53
|
+
assertChecksum(checksum, hashAlgoSync(payload))
|
|
54
|
+
return typedView(payload, format)
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const hash256sync = (x) => hashSync('sha256', hashSync('sha256', x, 'uint8'), 'uint8')
|
|
60
|
+
const hash256 = hash256sync // See note at the top
|
|
61
|
+
const {
|
|
62
|
+
encode: toBase58check,
|
|
63
|
+
decode: fromBase58check,
|
|
64
|
+
encodeSync: toBase58checkSync,
|
|
65
|
+
decodeSync: fromBase58checkSync,
|
|
66
|
+
} = makeBase58check(hash256, hash256sync)
|
|
67
|
+
|
|
68
|
+
export { toBase58check, fromBase58check, toBase58checkSync, fromBase58checkSync }
|
package/base64.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertUint8, assertEmptyRest } from './assert.js'
|
|
2
2
|
import { typedView } from './array.js'
|
|
3
|
+
import { decodeLatin1, encodeLatin1 } from './fallback/latin1.js'
|
|
3
4
|
import * as js from './fallback/base64.js'
|
|
4
5
|
|
|
5
6
|
// See https://datatracker.ietf.org/doc/html/rfc4648
|
|
@@ -7,42 +8,49 @@ import * as js from './fallback/base64.js'
|
|
|
7
8
|
// base64: A-Za-z0-9+/ and = if padding not disabled
|
|
8
9
|
// base64url: A-Za-z0-9_- and = if padding enabled
|
|
9
10
|
|
|
10
|
-
const { Buffer, atob } = globalThis // Buffer is optional, only used when native
|
|
11
|
+
const { Buffer, atob, btoa } = globalThis // Buffer is optional, only used when native
|
|
11
12
|
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
|
|
12
13
|
const { toBase64: web64 } = Uint8Array.prototype // Modern engines have this
|
|
13
14
|
|
|
14
15
|
const { E_CHAR, E_PADDING, E_LENGTH, E_LAST } = js
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
// faster only on Hermes (and a little in old Chrome), js path beats it on normal engines
|
|
18
|
+
const shouldUseBtoa = btoa && Boolean(globalThis.HermesInternal)
|
|
19
|
+
const shouldUseAtob = atob && Boolean(globalThis.HermesInternal)
|
|
17
20
|
|
|
18
21
|
// For native Buffer codepaths only
|
|
19
22
|
const isBuffer = (x) => x.constructor === Buffer && Buffer.isBuffer(x)
|
|
20
23
|
const toBuffer = (x) => (isBuffer(x) ? x : Buffer.from(x.buffer, x.byteOffset, x.byteLength))
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
assertUint8(x)
|
|
24
|
-
if (web64 && x.toBase64 === web64) {
|
|
25
|
-
return padding ? x.toBase64() : x.toBase64({ omitPadding: !padding }) // Modern, optionless is slightly faster
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (!haveNativeBuffer) return js.toBase64(x, false, padding) // Fallback
|
|
29
|
-
const res = toBuffer(x).toString('base64') // Older Node.js
|
|
25
|
+
function maybeUnpad(res, padding) {
|
|
30
26
|
if (padding) return res
|
|
31
27
|
const at = res.indexOf('=', res.length - 3)
|
|
32
28
|
return at === -1 ? res : res.slice(0, at)
|
|
33
29
|
}
|
|
34
30
|
|
|
31
|
+
function maybePad(res, padding) {
|
|
32
|
+
return padding && res.length % 4 !== 0 ? res + '='.repeat(4 - (res.length % 4)) : res
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const toUrl = (x) => x.replaceAll('+', '-').replaceAll('/', '_')
|
|
36
|
+
const fromUrl = (x) => x.replaceAll('-', '+').replaceAll('_', '/')
|
|
37
|
+
const haveWeb = (x) => web64 && x.toBase64 === web64
|
|
38
|
+
|
|
39
|
+
export function toBase64(x, { padding = true } = {}) {
|
|
40
|
+
assertUint8(x)
|
|
41
|
+
if (haveWeb(x)) return padding ? x.toBase64() : x.toBase64({ omitPadding: !padding }) // Modern, optionless is slightly faster
|
|
42
|
+
if (haveNativeBuffer) return maybeUnpad(toBuffer(x).toString('base64'), padding) // Older Node.js
|
|
43
|
+
if (shouldUseBtoa) return maybeUnpad(btoa(decodeLatin1(x)), padding)
|
|
44
|
+
return js.toBase64(x, false, padding) // Fallback
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
// NOTE: base64url omits padding by default
|
|
36
48
|
export function toBase64url(x, { padding = false } = {}) {
|
|
37
49
|
assertUint8(x)
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!haveNativeBuffer) return js.toBase64(x, true, padding) // Fallback
|
|
43
|
-
if (x.constructor === Buffer && Buffer.isBuffer(x)) return x.toString('base64url') // Older Node.js
|
|
44
|
-
const res = toBuffer(x).toString('base64url') // Older Node.js
|
|
45
|
-
return padding && res.length % 4 !== 0 ? res + '='.repeat(4 - (res.length % 4)) : res
|
|
50
|
+
if (haveWeb(x)) return x.toBase64({ alphabet: 'base64url', omitPadding: !padding }) // Modern
|
|
51
|
+
if (haveNativeBuffer) return maybePad(toBuffer(x).toString('base64url'), padding) // Older Node.js
|
|
52
|
+
if (shouldUseBtoa) return maybeUnpad(toUrl(btoa(decodeLatin1(x))), padding)
|
|
53
|
+
return js.toBase64(x, true, padding) // Fallback
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
// Unlike Buffer.from(), throws on invalid input (non-base64 symbols and incomplete chunks)
|
|
@@ -50,14 +58,17 @@ export function toBase64url(x, { padding = false } = {}) {
|
|
|
50
58
|
// NOTE: Always operates in strict mode for last chunk
|
|
51
59
|
|
|
52
60
|
// By default accepts both padded and non-padded variants, only strict base64
|
|
53
|
-
export function fromBase64(str, options
|
|
61
|
+
export function fromBase64(str, options) {
|
|
54
62
|
if (typeof options === 'string') options = { format: options } // Compat due to usage, TODO: remove
|
|
63
|
+
if (!options) return fromBase64common(str, false, 'both', 'uint8', null)
|
|
55
64
|
const { format = 'uint8', padding = 'both', ...rest } = options
|
|
56
65
|
return fromBase64common(str, false, padding, format, rest)
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
// By default accepts only non-padded strict base64url
|
|
60
|
-
export function fromBase64url(str,
|
|
69
|
+
export function fromBase64url(str, options) {
|
|
70
|
+
if (!options) return fromBase64common(str, true, false, 'uint8', null)
|
|
71
|
+
const { format = 'uint8', padding = false, ...rest } = options
|
|
61
72
|
return fromBase64common(str, true, padding, format, rest)
|
|
62
73
|
}
|
|
63
74
|
|
|
@@ -69,7 +80,7 @@ export function fromBase64any(str, { format = 'uint8', padding = 'both', ...rest
|
|
|
69
80
|
|
|
70
81
|
function fromBase64common(str, isBase64url, padding, format, rest) {
|
|
71
82
|
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
72
|
-
assertEmptyRest(rest)
|
|
83
|
+
if (rest !== null) assertEmptyRest(rest)
|
|
73
84
|
const auto = padding === 'both' ? str.endsWith('=') : undefined
|
|
74
85
|
// Older JSC supporting Uint8Array.fromBase64 lacks proper checks
|
|
75
86
|
if (padding === true || auto === true) {
|
|
@@ -84,49 +95,70 @@ function fromBase64common(str, isBase64url, padding, format, rest) {
|
|
|
84
95
|
throw new TypeError('Invalid padding option')
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
return typedView(fromBase64impl(str, isBase64url), format)
|
|
98
|
+
return typedView(fromBase64impl(str, isBase64url, padding), format)
|
|
88
99
|
}
|
|
89
100
|
|
|
90
101
|
// ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE
|
|
91
102
|
const ASCII_WHITESPACE = /[\t\n\f\r ]/ // non-u for JSC perf
|
|
92
103
|
|
|
104
|
+
function noWhitespaceSeen(str, arr) {
|
|
105
|
+
const at = str.indexOf('=', str.length - 3)
|
|
106
|
+
const paddingLength = at >= 0 ? str.length - at : 0
|
|
107
|
+
const chars = str.length - paddingLength
|
|
108
|
+
const e = chars % 4 // extra chars past blocks of 4
|
|
109
|
+
const b = arr.length - ((chars - e) / 4) * 3 // remaining bytes not covered by full blocks of chars
|
|
110
|
+
return (e === 0 && b === 0) || (e === 2 && b === 1) || (e === 3 && b === 2)
|
|
111
|
+
}
|
|
112
|
+
|
|
93
113
|
let fromBase64impl
|
|
94
114
|
if (Uint8Array.fromBase64) {
|
|
95
115
|
// NOTICE: this is actually slower than our JS impl in older JavaScriptCore and (slightly) in SpiderMonkey, but faster on V8 and new JavaScriptCore
|
|
96
|
-
fromBase64impl = (str, isBase64url) => {
|
|
116
|
+
fromBase64impl = (str, isBase64url, padding) => {
|
|
97
117
|
const alphabet = isBase64url ? 'base64url' : 'base64'
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
118
|
+
|
|
119
|
+
let arr
|
|
120
|
+
if (padding === true) {
|
|
121
|
+
// Padding is required from user, and we already checked that string length is divisible by 4
|
|
122
|
+
// Padding might still be wrong due to whitespace, but in that case native impl throws expected error
|
|
123
|
+
arr = Uint8Array.fromBase64(str, { alphabet, lastChunkHandling: 'strict' })
|
|
124
|
+
} else {
|
|
125
|
+
try {
|
|
126
|
+
const padded = str.length % 4 > 0 ? `${str}${'='.repeat(4 - (str.length % 4))}` : str
|
|
127
|
+
arr = Uint8Array.fromBase64(padded, { alphabet, lastChunkHandling: 'strict' })
|
|
128
|
+
} catch (err) {
|
|
129
|
+
// Normalize error: whitespace in input could have caused added padding to be invalid
|
|
130
|
+
// But reporting that as a padding error would be confusing
|
|
131
|
+
throw ASCII_WHITESPACE.test(str) ? new SyntaxError(E_CHAR) : err
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// We don't allow whitespace in input, but that can be rechecked based on output length
|
|
136
|
+
// All other chars are checked natively
|
|
137
|
+
if (!noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR)
|
|
138
|
+
return arr
|
|
101
139
|
}
|
|
102
140
|
} else {
|
|
103
|
-
fromBase64impl = (str, isBase64url) => {
|
|
141
|
+
fromBase64impl = (str, isBase64url, padding) => {
|
|
104
142
|
let arr
|
|
105
143
|
if (haveNativeBuffer) {
|
|
106
|
-
const invalidRegex = isBase64url ? /[^0-9a-z=_-]/iu : /[^0-9a-z=+/]/iu
|
|
107
|
-
if (invalidRegex.test(str)) throw new SyntaxError(E_CHAR)
|
|
108
|
-
const at = str.indexOf('=')
|
|
109
|
-
if (at >= 0 && /[^=]/iu.test(str.slice(at))) throw new SyntaxError(E_PADDING)
|
|
110
144
|
arr = Buffer.from(str, 'base64')
|
|
145
|
+
// Rechecking is cheaper than regexes on Node.js
|
|
146
|
+
const r = isBase64url ? maybeUnpad(str, padding === false) : maybePad(str, padding !== true)
|
|
147
|
+
if (r !== arr.toString(isBase64url ? 'base64url' : 'base64')) throw new SyntaxError(E_PADDING)
|
|
111
148
|
} else if (shouldUseAtob) {
|
|
112
149
|
// atob is faster than manual parsing on Hermes
|
|
113
150
|
if (isBase64url) {
|
|
114
151
|
if (/[\t\n\f\r +/]/.test(str)) throw new SyntaxError(E_CHAR) // atob verifies other invalid input
|
|
115
|
-
str = str
|
|
116
|
-
} else {
|
|
117
|
-
if (ASCII_WHITESPACE.test(str)) throw new SyntaxError(E_CHAR) // all other chars are checked natively
|
|
152
|
+
str = fromUrl(str)
|
|
118
153
|
}
|
|
119
154
|
|
|
120
|
-
let raw
|
|
121
155
|
try {
|
|
122
|
-
|
|
156
|
+
arr = encodeLatin1(atob(str))
|
|
123
157
|
} catch {
|
|
124
158
|
throw new SyntaxError(E_CHAR) // convert atob errors
|
|
125
159
|
}
|
|
126
160
|
|
|
127
|
-
|
|
128
|
-
arr = new Uint8Array(length)
|
|
129
|
-
for (let i = 0; i < length; i++) arr[i] = raw.charCodeAt(i)
|
|
161
|
+
if (!isBase64url && !noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR) // base64url checks input above
|
|
130
162
|
} else {
|
|
131
163
|
return js.fromBase64(str, isBase64url) // early return to skip last chunk verification, it's already validated in js
|
|
132
164
|
}
|
|
@@ -135,7 +167,7 @@ if (Uint8Array.fromBase64) {
|
|
|
135
167
|
// Check last chunk to be strict if it was incomplete
|
|
136
168
|
const expected = toBase64(arr.subarray(-(arr.length % 3)))
|
|
137
169
|
const end = str.length % 4 === 0 ? str.slice(-4) : str.slice(-(str.length % 4)).padEnd(4, '=')
|
|
138
|
-
const actual = isBase64url ? end
|
|
170
|
+
const actual = isBase64url ? fromUrl(end) : end
|
|
139
171
|
if (expected !== actual) throw new SyntaxError(E_LAST)
|
|
140
172
|
}
|
|
141
173
|
|
package/fallback/base32.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assertUint8 } from '../assert.js'
|
|
2
2
|
import { nativeEncoder, nativeDecoder } from './_utils.js'
|
|
3
|
+
import { encodeAscii } from './latin1.js'
|
|
3
4
|
|
|
4
5
|
// See https://datatracker.ietf.org/doc/html/rfc4648
|
|
5
6
|
|
|
@@ -13,6 +14,8 @@ export const E_PADDING = 'Invalid base32 padding'
|
|
|
13
14
|
export const E_LENGTH = 'Invalid base32 length'
|
|
14
15
|
export const E_LAST = 'Invalid last chunk'
|
|
15
16
|
|
|
17
|
+
const useTemplates = Boolean(globalThis.HermesInternal) // Faster on Hermes and JSC, but we use it only on Hermes
|
|
18
|
+
|
|
16
19
|
// We construct output by concatenating chars, this seems to be fine enough on modern JS engines
|
|
17
20
|
export function toBase32(arr, isBase32Hex, padding) {
|
|
18
21
|
assertUint8(arr)
|
|
@@ -54,13 +57,32 @@ export function toBase32(arr, isBase32Hex, padding) {
|
|
|
54
57
|
const c = arr[i + 2]
|
|
55
58
|
const d = arr[i + 3]
|
|
56
59
|
const e = arr[i + 4]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
const x0 = (a << 2) | (b >> 6) // 8 + 8 - 5 - 5 = 6 left
|
|
61
|
+
const x1 = ((b & 0x3f) << 4) | (c >> 4) // 6 + 8 - 5 - 5 = 4 left
|
|
62
|
+
const x2 = ((c & 0xf) << 6) | (d >> 2) // 4 + 8 - 5 - 5 = 2 left
|
|
63
|
+
const x3 = ((d & 0x3) << 8) | e // 2 + 8 - 5 - 5 = 0 left
|
|
64
|
+
oa[j] = codepairs[x0]
|
|
65
|
+
oa[j + 1] = codepairs[x1]
|
|
66
|
+
oa[j + 2] = codepairs[x2]
|
|
67
|
+
oa[j + 3] = codepairs[x3]
|
|
68
|
+
j += 4
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
o = nativeDecoder.decode(oa)
|
|
72
|
+
} else if (useTemplates) {
|
|
73
|
+
// Templates are faster only on Hermes and JSC. Browsers have TextDecoder anyway
|
|
74
|
+
for (; i < fullChunksBytes; i += 5) {
|
|
75
|
+
const a = arr[i]
|
|
76
|
+
const b = arr[i + 1]
|
|
77
|
+
const c = arr[i + 2]
|
|
78
|
+
const d = arr[i + 3]
|
|
79
|
+
const e = arr[i + 4]
|
|
80
|
+
const x0 = (a << 2) | (b >> 6) // 8 + 8 - 5 - 5 = 6 left
|
|
81
|
+
const x1 = ((b & 0x3f) << 4) | (c >> 4) // 6 + 8 - 5 - 5 = 4 left
|
|
82
|
+
const x2 = ((c & 0xf) << 6) | (d >> 2) // 4 + 8 - 5 - 5 = 2 left
|
|
83
|
+
const x3 = ((d & 0x3) << 8) | e // 2 + 8 - 5 - 5 = 0 left
|
|
84
|
+
o += `${pairs[x0]}${pairs[x1]}${pairs[x2]}${pairs[x3]}`
|
|
85
|
+
}
|
|
64
86
|
} else {
|
|
65
87
|
for (; i < fullChunksBytes; i += 5) {
|
|
66
88
|
const a = arr[i]
|
|
@@ -68,10 +90,14 @@ export function toBase32(arr, isBase32Hex, padding) {
|
|
|
68
90
|
const c = arr[i + 2]
|
|
69
91
|
const d = arr[i + 3]
|
|
70
92
|
const e = arr[i + 4]
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
const x0 = (a << 2) | (b >> 6) // 8 + 8 - 5 - 5 = 6 left
|
|
94
|
+
const x1 = ((b & 0x3f) << 4) | (c >> 4) // 6 + 8 - 5 - 5 = 4 left
|
|
95
|
+
const x2 = ((c & 0xf) << 6) | (d >> 2) // 4 + 8 - 5 - 5 = 2 left
|
|
96
|
+
const x3 = ((d & 0x3) << 8) | e // 2 + 8 - 5 - 5 = 0 left
|
|
97
|
+
o += pairs[x0]
|
|
98
|
+
o += pairs[x1]
|
|
99
|
+
o += pairs[x2]
|
|
100
|
+
o += pairs[x3]
|
|
75
101
|
}
|
|
76
102
|
}
|
|
77
103
|
|
|
@@ -97,7 +123,7 @@ export function toBase32(arr, isBase32Hex, padding) {
|
|
|
97
123
|
}
|
|
98
124
|
|
|
99
125
|
// TODO: can this be optimized? This only affects non-Hermes barebone engines though
|
|
100
|
-
const mapSize = nativeEncoder ?
|
|
126
|
+
const mapSize = nativeEncoder ? 128 : 65_536 // we have to store 64 KiB map or recheck everything if we can't decode to byte array
|
|
101
127
|
|
|
102
128
|
export function fromBase32(str, isBase32Hex) {
|
|
103
129
|
let inputLength = str.length
|
|
@@ -127,38 +153,47 @@ export function fromBase32(str, isBase32Hex) {
|
|
|
127
153
|
let i = 0
|
|
128
154
|
|
|
129
155
|
if (nativeEncoder) {
|
|
130
|
-
const codes =
|
|
131
|
-
|
|
132
|
-
while (i < mainLength) {
|
|
156
|
+
const codes = encodeAscii(str, E_CHAR)
|
|
157
|
+
for (; i < mainLength; i += 8) {
|
|
133
158
|
// each 5 bits, grouped 5 * 4 = 20
|
|
134
|
-
const
|
|
135
|
-
const
|
|
159
|
+
const x0 = codes[i]
|
|
160
|
+
const x1 = codes[i + 1]
|
|
161
|
+
const x2 = codes[i + 2]
|
|
162
|
+
const x3 = codes[i + 3]
|
|
163
|
+
const x4 = codes[i + 4]
|
|
164
|
+
const x5 = codes[i + 5]
|
|
165
|
+
const x6 = codes[i + 6]
|
|
166
|
+
const x7 = codes[i + 7]
|
|
167
|
+
const a = (m[x0] << 15) | (m[x1] << 10) | (m[x2] << 5) | m[x3]
|
|
168
|
+
const b = (m[x4] << 15) | (m[x5] << 10) | (m[x6] << 5) | m[x7]
|
|
136
169
|
if (a < 0 || b < 0) throw new SyntaxError(E_CHAR)
|
|
137
|
-
arr[at
|
|
138
|
-
arr[at
|
|
139
|
-
arr[at
|
|
140
|
-
arr[at
|
|
141
|
-
arr[at
|
|
170
|
+
arr[at] = a >> 12
|
|
171
|
+
arr[at + 1] = (a >> 4) & 0xff
|
|
172
|
+
arr[at + 2] = ((a << 4) & 0xff) | (b >> 16)
|
|
173
|
+
arr[at + 3] = (b >> 8) & 0xff
|
|
174
|
+
arr[at + 4] = b & 0xff
|
|
175
|
+
at += 5
|
|
142
176
|
}
|
|
143
177
|
} else {
|
|
144
|
-
|
|
178
|
+
for (; i < mainLength; i += 8) {
|
|
145
179
|
// each 5 bits, grouped 5 * 4 = 20
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
180
|
+
const x0 = str.charCodeAt(i)
|
|
181
|
+
const x1 = str.charCodeAt(i + 1)
|
|
182
|
+
const x2 = str.charCodeAt(i + 2)
|
|
183
|
+
const x3 = str.charCodeAt(i + 3)
|
|
184
|
+
const x4 = str.charCodeAt(i + 4)
|
|
185
|
+
const x5 = str.charCodeAt(i + 5)
|
|
186
|
+
const x6 = str.charCodeAt(i + 6)
|
|
187
|
+
const x7 = str.charCodeAt(i + 7)
|
|
188
|
+
const a = (m[x0] << 15) | (m[x1] << 10) | (m[x2] << 5) | m[x3]
|
|
189
|
+
const b = (m[x4] << 15) | (m[x5] << 10) | (m[x6] << 5) | m[x7]
|
|
156
190
|
if (a < 0 || b < 0) throw new SyntaxError(E_CHAR)
|
|
157
|
-
arr[at
|
|
158
|
-
arr[at
|
|
159
|
-
arr[at
|
|
160
|
-
arr[at
|
|
161
|
-
arr[at
|
|
191
|
+
arr[at] = a >> 12
|
|
192
|
+
arr[at + 1] = (a >> 4) & 0xff
|
|
193
|
+
arr[at + 2] = ((a << 4) & 0xff) | (b >> 16)
|
|
194
|
+
arr[at + 3] = (b >> 8) & 0xff
|
|
195
|
+
arr[at + 4] = b & 0xff
|
|
196
|
+
at += 5
|
|
162
197
|
}
|
|
163
198
|
}
|
|
164
199
|
|