@exodus/bytes 1.0.0-rc.6 → 1.0.0-rc.7
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/base58.js +3 -3
- package/base64.js +2 -2
- package/fallback/_utils.js +10 -1
- package/fallback/base32.js +2 -2
- package/fallback/base64.js +2 -2
- package/fallback/hex.js +2 -2
- package/fallback/latin1.js +35 -11
- package/hex.js +2 -16
- package/hex.node.js +26 -0
- package/package.json +16 -3
- package/utf8.node.js +33 -0
package/base58.js
CHANGED
|
@@ -1,7 +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
|
+
import { encodeAscii, decodeAscii } from './fallback/latin1.js'
|
|
5
5
|
|
|
6
6
|
const alphabet = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
|
|
7
7
|
const codes = new Uint8Array(alphabet.map((x) => x.charCodeAt(0)))
|
|
@@ -104,7 +104,7 @@ export function toBase58(arr) {
|
|
|
104
104
|
while (carry) {
|
|
105
105
|
const c = carry
|
|
106
106
|
carry = Math.floor(c / 58)
|
|
107
|
-
res
|
|
107
|
+
res.push(c - carry * 58)
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -112,7 +112,7 @@ export function toBase58(arr) {
|
|
|
112
112
|
const oa = new Uint8Array(res.length)
|
|
113
113
|
let j = 0
|
|
114
114
|
for (let i = res.length - 1; i >= 0; i--) oa[j++] = codes[res[i]]
|
|
115
|
-
return ZERO.repeat(zeros) +
|
|
115
|
+
return ZERO.repeat(zeros) + decodeAscii(oa)
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
let out = ''
|
package/base64.js
CHANGED
|
@@ -39,7 +39,7 @@ const haveWeb = (x) => web64 && x.toBase64 === web64
|
|
|
39
39
|
export function toBase64(x, { padding = true } = {}) {
|
|
40
40
|
assertUint8(x)
|
|
41
41
|
if (haveWeb(x)) return padding ? x.toBase64() : x.toBase64({ omitPadding: !padding }) // Modern, optionless is slightly faster
|
|
42
|
-
if (haveNativeBuffer) return maybeUnpad(toBuffer(x).
|
|
42
|
+
if (haveNativeBuffer) return maybeUnpad(toBuffer(x).base64Slice(0, x.byteLength), padding) // Older Node.js
|
|
43
43
|
if (shouldUseBtoa) return maybeUnpad(btoa(decodeLatin1(x)), padding)
|
|
44
44
|
return js.toBase64(x, false, padding) // Fallback
|
|
45
45
|
}
|
|
@@ -48,7 +48,7 @@ export function toBase64(x, { padding = true } = {}) {
|
|
|
48
48
|
export function toBase64url(x, { padding = false } = {}) {
|
|
49
49
|
assertUint8(x)
|
|
50
50
|
if (haveWeb(x)) return x.toBase64({ alphabet: 'base64url', omitPadding: !padding }) // Modern
|
|
51
|
-
if (haveNativeBuffer) return maybePad(toBuffer(x).
|
|
51
|
+
if (haveNativeBuffer) return maybePad(toBuffer(x).base64urlSlice(0, x.byteLength), padding) // Older Node.js
|
|
52
52
|
if (shouldUseBtoa) return maybeUnpad(toUrl(btoa(decodeLatin1(x))), padding)
|
|
53
53
|
return js.toBase64(x, true, padding) // Fallback
|
|
54
54
|
}
|
package/fallback/_utils.js
CHANGED
|
@@ -3,4 +3,13 @@ const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
|
|
|
3
3
|
const isNative = (x) => x && (haveNativeBuffer || `${x}`.includes('[native code]')) // we consider Node.js TextDecoder/TextEncoder native
|
|
4
4
|
const nativeEncoder = isNative(TextEncoder) ? new TextEncoder() : null
|
|
5
5
|
const nativeDecoder = isNative(TextDecoder) ? new TextDecoder('utf8', { ignoreBOM: true }) : null
|
|
6
|
-
|
|
6
|
+
const nativeBuffer = haveNativeBuffer ? Buffer : null
|
|
7
|
+
|
|
8
|
+
// Actually windows-1252, compatible with ascii and latin1 decoding
|
|
9
|
+
// Beware that on non-latin1, i.e. on windows-1252, this is broken in ~all Node.js versions released
|
|
10
|
+
// in 2025 due to a regression, so we call it Latin1 as it's usable only for that
|
|
11
|
+
const nativeDecoderLatin1 = isNative(TextDecoder)
|
|
12
|
+
? new TextDecoder('latin1', { ignoreBOM: true })
|
|
13
|
+
: null
|
|
14
|
+
|
|
15
|
+
export { nativeEncoder, nativeDecoder, nativeDecoderLatin1, nativeBuffer }
|
package/fallback/base32.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assertUint8 } from '../assert.js'
|
|
2
2
|
import { nativeEncoder, nativeDecoder } from './_utils.js'
|
|
3
|
-
import { encodeAscii } from './latin1.js'
|
|
3
|
+
import { encodeAscii, decodeAscii } from './latin1.js'
|
|
4
4
|
|
|
5
5
|
// See https://datatracker.ietf.org/doc/html/rfc4648
|
|
6
6
|
|
|
@@ -68,7 +68,7 @@ export function toBase32(arr, isBase32Hex, padding) {
|
|
|
68
68
|
j += 4
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
o =
|
|
71
|
+
o = decodeAscii(oa)
|
|
72
72
|
} else if (useTemplates) {
|
|
73
73
|
// Templates are faster only on Hermes and JSC. Browsers have TextDecoder anyway
|
|
74
74
|
for (; i < fullChunksBytes; i += 5) {
|
package/fallback/base64.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assertUint8 } from '../assert.js'
|
|
2
2
|
import { nativeEncoder, nativeDecoder } from './_utils.js'
|
|
3
|
-
import { encodeAscii } from './latin1.js'
|
|
3
|
+
import { encodeAscii, decodeAscii } from './latin1.js'
|
|
4
4
|
|
|
5
5
|
// See https://datatracker.ietf.org/doc/html/rfc4648
|
|
6
6
|
|
|
@@ -82,7 +82,7 @@ export function toBase64(arr, isURL, padding) {
|
|
|
82
82
|
oa[j + 1] = codepairs[((b & 0x0f) << 8) | c]
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
o =
|
|
85
|
+
o = decodeAscii(oa)
|
|
86
86
|
} else {
|
|
87
87
|
// This can be optimized by ~25% with templates on Hermes, but this codepath is not called on Hermes, it uses btoa
|
|
88
88
|
// Check git history for templates version
|
package/fallback/hex.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assertUint8 } from '../assert.js'
|
|
2
2
|
import { nativeDecoder, nativeEncoder } from './_utils.js'
|
|
3
|
-
import { encodeAscii } from './latin1.js'
|
|
3
|
+
import { encodeAscii, decodeAscii } from './latin1.js'
|
|
4
4
|
|
|
5
5
|
let hexArray // array of 256 bytes converted to two-char hex strings
|
|
6
6
|
let hexCodes // hexArray converted to u16 code pairs
|
|
@@ -97,7 +97,7 @@ export function toHex(arr) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
for (; i < length; i++) oa[i] = hexCodes[arr[i]]
|
|
100
|
-
return
|
|
100
|
+
return decodeAscii(oa)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
if (length > 30_000) {
|
package/fallback/latin1.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nativeEncoder } from './_utils.js'
|
|
1
|
+
import { nativeEncoder, nativeDecoder, nativeDecoderLatin1, nativeBuffer } from './_utils.js'
|
|
2
2
|
|
|
3
3
|
// See http://stackoverflow.com/a/22747272/680742, which says that lowest limit is in Chrome, with 0xffff args
|
|
4
4
|
// On Hermes, actual max is 0x20_000 minus current stack depth, 1/16 of that should be safe
|
|
@@ -32,6 +32,7 @@ export function asciiPrefix(arr) {
|
|
|
32
32
|
return length
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Capable of decoding Uint16Array to UTF-16 as well as Uint8Array to Latin-1
|
|
35
36
|
export function decodeLatin1(arr, start = 0, stop = arr.length) {
|
|
36
37
|
start |= 0
|
|
37
38
|
stop |= 0
|
|
@@ -52,10 +53,23 @@ export function decodeLatin1(arr, start = 0, stop = arr.length) {
|
|
|
52
53
|
return String.fromCharCode.apply(String, sliced)
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
// Does not check input, uses best available method
|
|
57
|
+
// Building an array for this is only faster than proper string concatenation when TextDecoder or native Buffer are available
|
|
58
|
+
export const decodeAscii = nativeBuffer
|
|
59
|
+
? (a) =>
|
|
60
|
+
// Buffer is faster on Node.js (but only for long enough data), if we know that output is ascii
|
|
61
|
+
a.byteLength >= 0x3_00
|
|
62
|
+
? nativeBuffer.from(a.buffer, a.byteOffset, a.byteLength).latin1Slice(0, a.byteLength) // .latin1Slice is faster than .asciiSlice
|
|
63
|
+
: nativeDecoder.decode(a) // On Node.js, utf8 decoder is faster than latin1
|
|
64
|
+
: nativeDecoderLatin1
|
|
65
|
+
? (a) => nativeDecoderLatin1.decode(a) // On browsers (specifically WebKit), latin1 decoder is faster than utf8
|
|
66
|
+
: (a) => decodeLatin1(new Uint8Array(a.buffer, a.byteOffset, a.byteLength)) // Fallback. We shouldn't get here, constructing with strings directly is faster
|
|
67
|
+
|
|
68
|
+
/* eslint-disable @exodus/mutable/no-param-reassign-prop-only */
|
|
69
|
+
|
|
70
|
+
export const encodeCharcodes = globalThis.HermesInternal
|
|
71
|
+
? (str, arr) => {
|
|
57
72
|
const length = str.length
|
|
58
|
-
const arr = new Uint8Array(length)
|
|
59
73
|
if (length > 64) {
|
|
60
74
|
const at = str.charCodeAt.bind(str) // faster on strings from ~64 chars on Hermes, but can be 10x slower on e.g. JSC
|
|
61
75
|
for (let i = 0; i < length; i++) arr[i] = at(i)
|
|
@@ -65,14 +79,17 @@ export const encodeLatin1 = globalThis.HermesInternal
|
|
|
65
79
|
|
|
66
80
|
return arr
|
|
67
81
|
}
|
|
68
|
-
: (str) => {
|
|
82
|
+
: (str, arr) => {
|
|
69
83
|
const length = str.length
|
|
70
|
-
const arr = new Uint8Array(length)
|
|
71
84
|
// Can be optimized with unrolling, but this is not used on non-Hermes atm
|
|
72
85
|
for (let i = 0; i < length; i++) arr[i] = str.charCodeAt(i)
|
|
73
86
|
return arr
|
|
74
87
|
}
|
|
75
88
|
|
|
89
|
+
/* eslint-enable @exodus/mutable/no-param-reassign-prop-only */
|
|
90
|
+
|
|
91
|
+
export const encodeLatin1 = (str) => encodeCharcodes(str, new Uint8Array(str.length))
|
|
92
|
+
|
|
76
93
|
// Expects nativeEncoder to be present
|
|
77
94
|
export const encodeAscii = globalThis.HermesInternal
|
|
78
95
|
? (str, ERR) => {
|
|
@@ -82,8 +99,15 @@ export const encodeAscii = globalThis.HermesInternal
|
|
|
82
99
|
if (info.read !== str.length || info.written !== str.length) throw new SyntaxError(ERR) // non-ascii
|
|
83
100
|
return codes.subarray(0, str.length)
|
|
84
101
|
}
|
|
85
|
-
:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
: nativeBuffer
|
|
103
|
+
? (str, ERR) => {
|
|
104
|
+
// TextEncoder is slow on Node.js 24 / 25 (was ok on 22)
|
|
105
|
+
const codes = nativeBuffer.from(str, 'utf8') // ascii/latin1 coerces, we need to check
|
|
106
|
+
if (codes.length !== str.length) throw new SyntaxError(ERR) // non-ascii
|
|
107
|
+
return new Uint8Array(codes.buffer, codes.byteOffset, codes.byteLength)
|
|
108
|
+
}
|
|
109
|
+
: (str, ERR) => {
|
|
110
|
+
const codes = nativeEncoder.encode(str)
|
|
111
|
+
if (codes.length !== str.length) throw new SyntaxError(ERR) // non-ascii
|
|
112
|
+
return codes
|
|
113
|
+
}
|
package/hex.js
CHANGED
|
@@ -2,30 +2,16 @@ import { assertUint8 } from './assert.js'
|
|
|
2
2
|
import { typedView } from './array.js'
|
|
3
3
|
import * as js from './fallback/hex.js'
|
|
4
4
|
|
|
5
|
-
const { Buffer } = globalThis // Buffer is optional, only used when native
|
|
6
|
-
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
|
|
7
5
|
const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
|
|
8
6
|
|
|
9
|
-
const { E_HEX } = js
|
|
10
|
-
|
|
11
7
|
export function toHex(arr) {
|
|
12
8
|
assertUint8(arr)
|
|
13
9
|
if (arr.length === 0) return ''
|
|
14
10
|
if (webHex && arr.toHex === webHex) return arr.toHex()
|
|
15
|
-
|
|
16
|
-
if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.toString('hex')
|
|
17
|
-
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString('hex')
|
|
11
|
+
return js.toHex(arr)
|
|
18
12
|
}
|
|
19
13
|
|
|
20
14
|
// Unlike Buffer.from(), throws on invalid input
|
|
21
15
|
export const fromHex = Uint8Array.fromHex
|
|
22
16
|
? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
|
|
23
|
-
:
|
|
24
|
-
? (str, format = 'uint8') => {
|
|
25
|
-
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
26
|
-
if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
|
|
27
|
-
const buf = Buffer.from(str, 'hex') // will stop on first non-hex character, so we can just validate length
|
|
28
|
-
if (buf.length * 2 !== str.length) throw new SyntaxError(E_HEX)
|
|
29
|
-
return typedView(buf, format)
|
|
30
|
-
}
|
|
31
|
-
: (str, format = 'uint8') => typedView(js.fromHex(str), format)
|
|
17
|
+
: (str, format = 'uint8') => typedView(js.fromHex(str), format)
|
package/hex.node.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { assertUint8 } from './assert.js'
|
|
2
|
+
import { typedView } from './array.js'
|
|
3
|
+
import { E_HEX } from './fallback/hex.js'
|
|
4
|
+
|
|
5
|
+
if (Buffer.TYPED_ARRAY_SUPPORT) throw new Error('Unexpected Buffer polyfill')
|
|
6
|
+
|
|
7
|
+
const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
|
|
8
|
+
|
|
9
|
+
export function toHex(arr) {
|
|
10
|
+
assertUint8(arr)
|
|
11
|
+
if (arr.length === 0) return ''
|
|
12
|
+
if (webHex && arr.toHex === webHex) return arr.toHex()
|
|
13
|
+
if (arr.constructor === Buffer && Buffer.isBuffer(arr)) return arr.hexSlice(0, arr.byteLength)
|
|
14
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).hexSlice(0, arr.byteLength)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Unlike Buffer.from(), throws on invalid input
|
|
18
|
+
export const fromHex = Uint8Array.fromHex
|
|
19
|
+
? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
|
|
20
|
+
: (str, format = 'uint8') => {
|
|
21
|
+
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
22
|
+
if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
|
|
23
|
+
const buf = Buffer.from(str, 'hex') // will stop on first non-hex character, so we can just validate length
|
|
24
|
+
if (buf.length * 2 !== str.length) throw new SyntaxError(E_HEX)
|
|
25
|
+
return typedView(buf, format)
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bytes",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.7",
|
|
4
4
|
"description": "Various operations on Uint8Array data",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint .",
|
|
@@ -50,7 +50,9 @@
|
|
|
50
50
|
"/base58.js",
|
|
51
51
|
"/base58check.js",
|
|
52
52
|
"/base64.js",
|
|
53
|
+
"/hex.node.js",
|
|
53
54
|
"/hex.js",
|
|
55
|
+
"/utf8.node.js",
|
|
54
56
|
"/utf8.js"
|
|
55
57
|
],
|
|
56
58
|
"exports": {
|
|
@@ -59,8 +61,18 @@
|
|
|
59
61
|
"./base58.js": "./base58.js",
|
|
60
62
|
"./base58check.js": "./base58check.js",
|
|
61
63
|
"./base64.js": "./base64.js",
|
|
62
|
-
"./hex.js":
|
|
63
|
-
|
|
64
|
+
"./hex.js": {
|
|
65
|
+
"node": "./hex.node.js",
|
|
66
|
+
"default": "./hex.js"
|
|
67
|
+
},
|
|
68
|
+
"./utf16.js": {
|
|
69
|
+
"node": "./utf16.node.js",
|
|
70
|
+
"default": "./utf16.js"
|
|
71
|
+
},
|
|
72
|
+
"./utf8.js": {
|
|
73
|
+
"node": "./utf8.node.js",
|
|
74
|
+
"default": "./utf8.js"
|
|
75
|
+
}
|
|
64
76
|
},
|
|
65
77
|
"peerDependencies": {
|
|
66
78
|
"@exodus/crypto": "^1.0.0-rc.4"
|
|
@@ -94,6 +106,7 @@
|
|
|
94
106
|
"fast-base64-encode": "^1.0.0",
|
|
95
107
|
"hextreme": "^1.0.7",
|
|
96
108
|
"hi-base32": "^0.5.1",
|
|
109
|
+
"iconv-lite": "^0.7.0",
|
|
97
110
|
"jsvu": "^3.0.0",
|
|
98
111
|
"text-encoding": "^0.7.0",
|
|
99
112
|
"typescript": "^5.9.3"
|
package/utf8.node.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { assertUint8 } from './assert.js'
|
|
2
|
+
import { typedView } from './array.js'
|
|
3
|
+
import { E_STRICT_UNICODE } from './fallback/utf8.js'
|
|
4
|
+
import { isAscii } from 'node:buffer'
|
|
5
|
+
|
|
6
|
+
if (Buffer.TYPED_ARRAY_SUPPORT) throw new Error('Unexpected Buffer polyfill')
|
|
7
|
+
|
|
8
|
+
const decoderFatal = new TextDecoder('utf8', { ignoreBOM: true, fatal: true })
|
|
9
|
+
const decoderLoose = new TextDecoder('utf8', { ignoreBOM: true })
|
|
10
|
+
const { isWellFormed } = String.prototype
|
|
11
|
+
|
|
12
|
+
function encode(str, loose = false) {
|
|
13
|
+
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
14
|
+
const res = Buffer.from(str)
|
|
15
|
+
if (loose || str.length === res.length || isWellFormed.call(str)) return res // length is equal only for ascii, which is automatically fine
|
|
16
|
+
throw new TypeError(E_STRICT_UNICODE)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function decode(arr, loose = false) {
|
|
20
|
+
assertUint8(arr)
|
|
21
|
+
if (isAscii(arr)) {
|
|
22
|
+
// On non-ascii strings, this loses ~10% * [relative position of the first non-ascii byte] (up to 10% total)
|
|
23
|
+
// On ascii strings, this wins 1.5x on loose = false and 1.3x on loose = true
|
|
24
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).latin1Slice(0, arr.byteLength) // .latin1Slice is faster than .asciiSlice
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return loose ? decoderLoose.decode(arr) : decoderFatal.decode(arr)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const utf8fromString = (str, format = 'uint8') => typedView(encode(str, false), format)
|
|
31
|
+
export const utf8fromStringLoose = (str, format = 'uint8') => typedView(encode(str, true), format)
|
|
32
|
+
export const utf8toString = (arr) => decode(arr, false)
|
|
33
|
+
export const utf8toStringLoose = (arr) => decode(arr, true)
|