@exodus/bytes 1.0.0-rc.5 → 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/base32.js +12 -5
- package/base58.js +2 -2
- package/base58check.js +49 -11
- package/base64.js +72 -40
- package/fallback/base32.js +70 -35
- package/fallback/base64.js +56 -26
- package/fallback/hex.js +137 -46
- package/fallback/latin1.js +89 -0
- package/fallback/utf8.js +77 -112
- package/package.json +7 -4
- package/utf8.js +10 -46
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
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { typedView } from './array.js'
|
|
2
2
|
import { assertUint8 } from './assert.js'
|
|
3
3
|
import { nativeDecoder, nativeEncoder } from './fallback/_utils.js'
|
|
4
|
+
import { encodeAscii } from './fallback/latin1.js'
|
|
4
5
|
|
|
5
6
|
const alphabet = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
|
|
6
7
|
const codes = new Uint8Array(alphabet.map((x) => x.charCodeAt(0)))
|
|
@@ -142,8 +143,7 @@ export function fromBase58(str, format = 'uint8') {
|
|
|
142
143
|
|
|
143
144
|
// nativeEncoder gives a benefit here
|
|
144
145
|
if (nativeEncoder) {
|
|
145
|
-
const codes =
|
|
146
|
-
if (codes.length !== str.length) throw new SyntaxError(E_CHAR) // non-ascii
|
|
146
|
+
const codes = encodeAscii(str, E_CHAR)
|
|
147
147
|
for (let i = zeros; i < length; i++) {
|
|
148
148
|
const c = fromMap[codes[i]]
|
|
149
149
|
if (c < 0) throw new SyntaxError(E_CHAR)
|
package/base58check.js
CHANGED
|
@@ -6,25 +6,63 @@ import { hashSync } from '@exodus/crypto/hash'
|
|
|
6
6
|
// Note: while API is async, we use hashSync for now until we improve webcrypto perf for hash256
|
|
7
7
|
// Inputs to base58 are typically very small, and that makes a difference
|
|
8
8
|
|
|
9
|
-
const hash256 = (x) => hashSync('sha256', hashSync('sha256', x, 'uint8'), 'uint8')
|
|
10
|
-
|
|
11
9
|
const E_CHECKSUM = 'Invalid checksum'
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
16
15
|
const res = new Uint8Array(arr.length + 4)
|
|
17
16
|
res.set(arr, 0)
|
|
18
17
|
res.set(checksum.subarray(0, 4), arr.length)
|
|
19
18
|
return toBase58(res)
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
function decodeWithChecksum(str) {
|
|
23
22
|
const arr = fromBase58(str) // checks input
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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) {
|
|
28
29
|
if ((c[0] ^ r[0]) | (c[1] ^ r[1]) | (c[2] ^ r[2]) | (c[3] ^ r[3])) throw new Error(E_CHECKSUM)
|
|
29
|
-
return typedView(payload, format)
|
|
30
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
|
|
package/fallback/base64.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,13 +14,10 @@ export const E_PADDING = 'Invalid base64 padding'
|
|
|
13
14
|
export const E_LENGTH = 'Invalid base64 length'
|
|
14
15
|
export const E_LAST = 'Invalid last chunk'
|
|
15
16
|
|
|
16
|
-
// Alternatively, we could have mapped 0-255 bytes to charcodes and just used btoa(ascii),
|
|
17
|
-
// but that approach is _slower_ than our toBase64js function, even on Hermes
|
|
18
|
-
|
|
19
17
|
// We construct output by concatenating chars, this seems to be fine enough on modern JS engines
|
|
20
18
|
export function toBase64(arr, isURL, padding) {
|
|
21
19
|
assertUint8(arr)
|
|
22
|
-
const fullChunks =
|
|
20
|
+
const fullChunks = (arr.length / 3) | 0
|
|
23
21
|
const fullChunksBytes = fullChunks * 3
|
|
24
22
|
let o = ''
|
|
25
23
|
let i = 0
|
|
@@ -51,21 +49,49 @@ export function toBase64(arr, isURL, padding) {
|
|
|
51
49
|
// This whole loop can be commented out, the algorithm won't change, it's just an optimization of the next loop
|
|
52
50
|
if (nativeDecoder) {
|
|
53
51
|
const oa = new Uint16Array(fullChunks * 2)
|
|
54
|
-
|
|
52
|
+
let j = 0
|
|
53
|
+
for (const last = arr.length - 11; i < last; i += 12, j += 8) {
|
|
54
|
+
const x0 = arr[i]
|
|
55
|
+
const x1 = arr[i + 1]
|
|
56
|
+
const x2 = arr[i + 2]
|
|
57
|
+
const x3 = arr[i + 3]
|
|
58
|
+
const x4 = arr[i + 4]
|
|
59
|
+
const x5 = arr[i + 5]
|
|
60
|
+
const x6 = arr[i + 6]
|
|
61
|
+
const x7 = arr[i + 7]
|
|
62
|
+
const x8 = arr[i + 8]
|
|
63
|
+
const x9 = arr[i + 9]
|
|
64
|
+
const x10 = arr[i + 10]
|
|
65
|
+
const x11 = arr[i + 11]
|
|
66
|
+
oa[j] = codepairs[(x0 << 4) | (x1 >> 4)]
|
|
67
|
+
oa[j + 1] = codepairs[((x1 & 0x0f) << 8) | x2]
|
|
68
|
+
oa[j + 2] = codepairs[(x3 << 4) | (x4 >> 4)]
|
|
69
|
+
oa[j + 3] = codepairs[((x4 & 0x0f) << 8) | x5]
|
|
70
|
+
oa[j + 4] = codepairs[(x6 << 4) | (x7 >> 4)]
|
|
71
|
+
oa[j + 5] = codepairs[((x7 & 0x0f) << 8) | x8]
|
|
72
|
+
oa[j + 6] = codepairs[(x9 << 4) | (x10 >> 4)]
|
|
73
|
+
oa[j + 7] = codepairs[((x10 & 0x0f) << 8) | x11]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// i < last here is equivalent to i < fullChunksBytes
|
|
77
|
+
for (const last = arr.length - 2; i < last; i += 3, j += 2) {
|
|
55
78
|
const a = arr[i]
|
|
56
79
|
const b = arr[i + 1]
|
|
57
80
|
const c = arr[i + 2]
|
|
58
|
-
oa[j
|
|
59
|
-
oa[j
|
|
81
|
+
oa[j] = codepairs[(a << 4) | (b >> 4)]
|
|
82
|
+
oa[j + 1] = codepairs[((b & 0x0f) << 8) | c]
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
o = nativeDecoder.decode(oa)
|
|
63
86
|
} else {
|
|
87
|
+
// This can be optimized by ~25% with templates on Hermes, but this codepath is not called on Hermes, it uses btoa
|
|
88
|
+
// Check git history for templates version
|
|
64
89
|
for (; i < fullChunksBytes; i += 3) {
|
|
65
90
|
const a = arr[i]
|
|
66
91
|
const b = arr[i + 1]
|
|
67
92
|
const c = arr[i + 2]
|
|
68
|
-
o += pairs[(a << 4) | (b >> 4)]
|
|
93
|
+
o += pairs[(a << 4) | (b >> 4)]
|
|
94
|
+
o += pairs[((b & 0x0f) << 8) | c]
|
|
69
95
|
}
|
|
70
96
|
}
|
|
71
97
|
|
|
@@ -92,9 +118,8 @@ export function toBase64(arr, isURL, padding) {
|
|
|
92
118
|
}
|
|
93
119
|
|
|
94
120
|
// TODO: can this be optimized? This only affects non-Hermes barebone engines though
|
|
95
|
-
const mapSize = nativeEncoder ?
|
|
121
|
+
const mapSize = nativeEncoder ? 128 : 65_536 // we have to store 64 KiB map or recheck everything if we can't decode to byte array
|
|
96
122
|
|
|
97
|
-
// Last chunk is rechecked at API
|
|
98
123
|
export function fromBase64(str, isURL) {
|
|
99
124
|
let inputLength = str.length
|
|
100
125
|
while (str[inputLength - 1] === '=') inputLength--
|
|
@@ -121,26 +146,31 @@ export function fromBase64(str, isURL) {
|
|
|
121
146
|
let i = 0
|
|
122
147
|
|
|
123
148
|
if (nativeEncoder) {
|
|
124
|
-
const codes =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
149
|
+
const codes = encodeAscii(str, E_CHAR)
|
|
150
|
+
for (; i < mainLength; i += 4) {
|
|
151
|
+
const c0 = codes[i]
|
|
152
|
+
const c1 = codes[i + 1]
|
|
153
|
+
const c2 = codes[i + 2]
|
|
154
|
+
const c3 = codes[i + 3]
|
|
155
|
+
const a = (m[c0] << 18) | (m[c1] << 12) | (m[c2] << 6) | m[c3]
|
|
128
156
|
if (a < 0) throw new SyntaxError(E_CHAR)
|
|
129
|
-
arr[at
|
|
130
|
-
arr[at
|
|
131
|
-
arr[at
|
|
157
|
+
arr[at] = a >> 16
|
|
158
|
+
arr[at + 1] = (a >> 8) & 0xff
|
|
159
|
+
arr[at + 2] = a & 0xff
|
|
160
|
+
at += 3
|
|
132
161
|
}
|
|
133
162
|
} else {
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
163
|
+
for (; i < mainLength; i += 4) {
|
|
164
|
+
const c0 = str.charCodeAt(i)
|
|
165
|
+
const c1 = str.charCodeAt(i + 1)
|
|
166
|
+
const c2 = str.charCodeAt(i + 2)
|
|
167
|
+
const c3 = str.charCodeAt(i + 3)
|
|
168
|
+
const a = (m[c0] << 18) | (m[c1] << 12) | (m[c2] << 6) | m[c3]
|
|
140
169
|
if (a < 0) throw new SyntaxError(E_CHAR)
|
|
141
|
-
arr[at
|
|
142
|
-
arr[at
|
|
143
|
-
arr[at
|
|
170
|
+
arr[at] = a >> 16
|
|
171
|
+
arr[at + 1] = (a >> 8) & 0xff
|
|
172
|
+
arr[at + 2] = a & 0xff
|
|
173
|
+
at += 3
|
|
144
174
|
}
|
|
145
175
|
}
|
|
146
176
|
|