@exodus/bytes 1.0.0-rc.8 → 1.0.0
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/LICENSE +1 -1
- package/README.md +286 -4
- package/array.d.ts +24 -0
- package/base58.js +16 -8
- package/base64.d.ts +76 -0
- package/bigint.js +14 -0
- package/encoding-lite.js +7 -0
- package/encoding.js +12 -0
- package/fallback/_utils.js +100 -10
- package/fallback/encoding.js +290 -0
- package/fallback/encoding.labels.js +46 -0
- package/fallback/encoding.util.js +34 -0
- package/fallback/hex.js +2 -70
- package/fallback/latin1.js +2 -1
- package/fallback/multi-byte.encodings.cjs +1 -0
- package/fallback/multi-byte.encodings.json +545 -0
- package/fallback/multi-byte.js +448 -0
- package/fallback/multi-byte.table.js +114 -0
- package/fallback/single-byte.encodings.js +61 -0
- package/fallback/single-byte.js +86 -0
- package/fallback/utf16.js +180 -0
- package/hex.d.ts +22 -0
- package/hex.node.js +2 -0
- package/multi-byte.js +13 -0
- package/multi-byte.node.js +25 -0
- package/package.json +62 -13
- package/single-byte.js +54 -0
- package/single-byte.node.js +62 -0
- package/utf16.js +73 -0
- package/utf16.node.js +79 -0
- package/utf8.d.ts +42 -0
- package/utf8.js +7 -9
- package/utf8.node.js +8 -5
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,27 +1,309 @@
|
|
|
1
1
|
# `@exodus/bytes`
|
|
2
2
|
|
|
3
|
-
`Uint8Array` conversion to and from `base64`, `base32`, `base58`, `hex`, and `
|
|
3
|
+
`Uint8Array` conversion to and from `base64`, `base32`, `base58`, `hex`, `utf8`, `utf16`, `bech32` and `wif`
|
|
4
4
|
|
|
5
|
-
[
|
|
5
|
+
And a [`TextEncoder` / `TextDecoder` polyfill](#textencoder--textdecoder-polyfill)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Strict
|
|
8
|
+
|
|
9
|
+
Performs proper input validation, ensures no garbage-in-garbage-out
|
|
10
|
+
|
|
11
|
+
Tested on Node.js, Deno, Bun, browsers (including Servo), Hermes, QuickJS and barebone engines in CI [(how?)](https://github.com/ExodusMovement/test#exodustest)
|
|
12
|
+
|
|
13
|
+
## Fast
|
|
14
|
+
|
|
15
|
+
* `10-20x` faster than `Buffer` polyfill
|
|
16
|
+
* `2-10x` faster than `iconv-lite`
|
|
17
|
+
|
|
18
|
+
The above was for the js fallback
|
|
19
|
+
|
|
20
|
+
It's up to `100x` when native impl is available \
|
|
21
|
+
e.g. in `utf8fromString` on Hermes / React Native or `fromHex` in Chrome
|
|
22
|
+
|
|
23
|
+
Also:
|
|
24
|
+
* `3-8x` faster than `bs58`
|
|
25
|
+
* `10-30x` faster than `@scure/base` (or `>100x` on Node.js <25)
|
|
26
|
+
* Faster in `utf8toString` / `utf8fromString` than `Buffer` or `TextDecoder` / `TextEncoder` on Node.js
|
|
27
|
+
|
|
28
|
+
See [Performance](./Performance.md) for more info
|
|
29
|
+
|
|
30
|
+
## TextEncoder / TextDecoder polyfill
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding.js'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Less than half the bundle size of [text-encoding](https://npmjs.com/text-encoding), [whatwg-encoding](https://npmjs.com/whatwg-encoding) or [iconv-lite](https://npmjs.com/iconv-lite) (gzipped or not), and [is much faster](#fast).
|
|
37
|
+
See also [lite version](#lite-version).
|
|
38
|
+
|
|
39
|
+
Spec compliant, passing WPT and covered with extra tests.
|
|
40
|
+
|
|
41
|
+
Moreover, tests for this library uncovered [bugs in all major implementations](https://docs.google.com/spreadsheets/d/1pdEefRG6r9fZy61WHGz0TKSt8cO4ISWqlpBN5KntIvQ/edit).
|
|
42
|
+
|
|
43
|
+
[Faster than Node.js native implementation on Node.js](https://github.com/nodejs/node/issues/61041#issuecomment-3649242024).
|
|
44
|
+
|
|
45
|
+
### Caveat: `TextDecoder` / `TextEncoder` APIs are lossy by default per spec
|
|
46
|
+
|
|
47
|
+
_These are only provided as a compatibility layer, prefer hardened APIs instead in new code._
|
|
48
|
+
|
|
49
|
+
* `TextDecoder` can (and should) be used with `{ fatal: true }` option for all purposes demanding correctness / lossless transforms
|
|
50
|
+
|
|
51
|
+
* `TextEncoder` does not support a fatal mode per spec, it always performs replacement.
|
|
52
|
+
|
|
53
|
+
That is not suitable for hashing, cryptography or consensus applications.\
|
|
54
|
+
Otherwise there would be non-equal strings with equal signatures and hashes — the collision is caused by the lossy transform of a JS string to bytes.
|
|
55
|
+
Those also survive e.g. `JSON.stringify`/`JSON.parse` or being sent over network.
|
|
56
|
+
|
|
57
|
+
Use strict APIs in new applications, see `utf8fromString` / `utf16fromString` below.\
|
|
58
|
+
Those throw on non-well-formed strings by default.
|
|
59
|
+
|
|
60
|
+
### Lite version
|
|
61
|
+
|
|
62
|
+
If you don't need support for legacy multi-byte encodings, you can use the lite import:
|
|
63
|
+
```js
|
|
64
|
+
import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding-lite.js'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This reduces the bundle size 10x:\
|
|
68
|
+
from 90 KiB gzipped for `@exodus/bytes/encoding.js` to 9 KiB gzipped for `@exodus/bytes/encoding-lite.js`.\
|
|
69
|
+
(For comparison, `text-encoding` module is 190 KiB gzipped, and `iconv-lite` is 194 KiB gzipped).
|
|
70
|
+
|
|
71
|
+
It still supports `utf-8`, `utf-16le`, `utf-16be` and all single-byte encodings specified by the spec,
|
|
72
|
+
the only difference is support for legacy multi-byte encodings.
|
|
73
|
+
|
|
74
|
+
See [the list of encodings](https://encoding.spec.whatwg.org/#names-and-labels).
|
|
8
75
|
|
|
9
76
|
## API
|
|
10
77
|
|
|
78
|
+
### `@exodus/bytes/utf8.js`
|
|
79
|
+
|
|
80
|
+
##### `utf8fromString(str, format = 'uint8')`
|
|
81
|
+
##### `utf8fromStringLoose(str, format = 'uint8')`
|
|
82
|
+
##### `utf8toString(arr)`
|
|
83
|
+
##### `utf8toStringLoose(arr)`
|
|
84
|
+
|
|
85
|
+
### `@exodus/bytes/utf16.js`
|
|
86
|
+
|
|
87
|
+
##### `utf16fromString(str, format = 'uint16')`
|
|
88
|
+
##### `utf16fromStringLoose(str, format = 'uint16')`
|
|
89
|
+
##### `utf16toString(arr, 'uint16')`
|
|
90
|
+
##### `utf16toStringLoose(arr, 'uint16')`
|
|
91
|
+
|
|
92
|
+
### `@exodus/bytes/single-byte.js`
|
|
93
|
+
|
|
94
|
+
##### `createSinglebyteDecoder(encoding, loose = false)`
|
|
95
|
+
|
|
96
|
+
Create a decoder for a supported one-byte `encoding`.
|
|
97
|
+
|
|
98
|
+
Returns a function `decode(arr)` that decodes bytes to a string.
|
|
99
|
+
|
|
100
|
+
### `@exodus/bytes/multi-byte.js`
|
|
101
|
+
|
|
102
|
+
##### `createMultibyteDecoder(encoding, loose = false)`
|
|
103
|
+
|
|
104
|
+
Create a decoder for a supported legacy multi-byte `encoding`.
|
|
105
|
+
|
|
106
|
+
Returns a function `decode(arr, stream = false)` that decodes bytes to a string.
|
|
107
|
+
|
|
108
|
+
That function will have state while `stream = true` is used.
|
|
109
|
+
|
|
110
|
+
##### `windows1252toString(arr)`
|
|
111
|
+
|
|
112
|
+
Decode `windows-1252` bytes to a string.
|
|
113
|
+
|
|
114
|
+
Also supports `ascii` and `latin-1` as those are strict subsets of `windows-1252`.
|
|
115
|
+
|
|
116
|
+
There is no loose variant for this encoding, all bytes can be decoded.
|
|
117
|
+
|
|
118
|
+
Same as `windows1252toString = createSinglebyteDecoder('windows-1252')`.
|
|
119
|
+
|
|
120
|
+
### `@exodus/bytes/bigint.js`
|
|
121
|
+
|
|
122
|
+
##### `fromBigInt(bigint, { length, format = 'uint8' })`
|
|
123
|
+
##### `toBigInt(arr)`
|
|
124
|
+
|
|
11
125
|
### `@exodus/bytes/hex.js`
|
|
12
126
|
|
|
127
|
+
##### `toHex(arr)`
|
|
128
|
+
##### `fromHex(string)`
|
|
129
|
+
|
|
13
130
|
### `@exodus/bytes/base64.js`
|
|
14
131
|
|
|
132
|
+
##### `toBase64(arr, { padding = true })`
|
|
133
|
+
##### `toBase64url(arr, { padding = false })`
|
|
134
|
+
##### `fromBase64(str, { format = 'uint8', padding = 'both' })`
|
|
135
|
+
##### `fromBase64url(str, { format = 'uint8', padding = false })`
|
|
136
|
+
##### `fromBase64any(str, { format = 'uint8', padding = 'both' })`
|
|
137
|
+
|
|
15
138
|
### `@exodus/bytes/base32.js`
|
|
16
139
|
|
|
17
|
-
|
|
140
|
+
##### `toBase32(arr, { padding = false })`
|
|
141
|
+
##### `toBase32hex(arr, { padding = false })`
|
|
142
|
+
##### `fromBase32(str, { format = 'uint8', padding = 'both' })`
|
|
143
|
+
##### `fromBase32hex(str, { format = 'uint8', padding = 'both' })`
|
|
144
|
+
|
|
145
|
+
### `@exodus/bytes/bech32.js`
|
|
146
|
+
|
|
147
|
+
##### `getPrefix(str, limit = 90)`
|
|
148
|
+
##### `toBech32(prefix, bytes, limit = 90)`
|
|
149
|
+
##### `fromBech32(str, limit = 90)`
|
|
150
|
+
##### `toBech32m(prefix, bytes, limit = 90)`
|
|
151
|
+
##### `fromBech32m(str, limit = 90)`
|
|
18
152
|
|
|
19
153
|
### `@exodus/bytes/base58.js`
|
|
20
154
|
|
|
155
|
+
##### `toBase58(arr)`
|
|
156
|
+
##### `fromBase58(str, format = 'uint8')`
|
|
157
|
+
|
|
158
|
+
##### `toBase58xrp(arr)`
|
|
159
|
+
##### `fromBase58xrp(str, format = 'uint8')`
|
|
160
|
+
|
|
21
161
|
### `@exodus/bytes/base58check.js`
|
|
22
162
|
|
|
163
|
+
##### `async toBase58check(arr)`
|
|
164
|
+
##### `toBase58checkSync(arr)`
|
|
165
|
+
##### `async fromBase58check(str, format = 'uint8')`
|
|
166
|
+
##### `fromBase58checkSync(str, format = 'uint8')`
|
|
167
|
+
##### `makeBase58check(hashAlgo, hashAlgoSync)`
|
|
168
|
+
|
|
23
169
|
### `@exodus/bytes/wif.js`
|
|
24
170
|
|
|
171
|
+
##### `async fromWifString(string, version)`
|
|
172
|
+
##### `fromWifStringSync(string, version)`
|
|
173
|
+
##### `async toWifString({ version, privateKey, compressed })`
|
|
174
|
+
##### `toWifStringSync({ version, privateKey, compressed })`
|
|
175
|
+
|
|
176
|
+
### `@exodus/bytes/encoding.js`
|
|
177
|
+
|
|
178
|
+
Implements the [Encoding standard](https://encoding.spec.whatwg.org/):
|
|
179
|
+
[TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder),
|
|
180
|
+
[TextEncoder](https://encoding.spec.whatwg.org/#interface-textdecoder),
|
|
181
|
+
some [hooks](https://encoding.spec.whatwg.org/#specification-hooks) (see below).
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
import { TextDecoder, TextDecoder } from '@exodus/bytes/encoding.js'
|
|
185
|
+
|
|
186
|
+
// Hooks for standards
|
|
187
|
+
import { getBOMEncoding, legacyHookDecode, normalizeEncoding } from '@exodus/bytes/encoding.js'
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `new TextDecoder(label = 'utf-8', { fatal = false, ignoreBOM = false })`
|
|
191
|
+
|
|
192
|
+
[TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) implementation/polyfill.
|
|
193
|
+
|
|
194
|
+
#### `new TextEncoder()`
|
|
195
|
+
|
|
196
|
+
[TextEncoder](https://encoding.spec.whatwg.org/#interface-textdecoder) implementation/polyfill.
|
|
197
|
+
|
|
198
|
+
#### `normalizeEncoding(label)`
|
|
199
|
+
|
|
200
|
+
Implements [get an encoding from a string `label`](https://encoding.spec.whatwg.org/#concept-encoding-get).
|
|
201
|
+
|
|
202
|
+
Converts an encoding [label](https://encoding.spec.whatwg.org/#names-and-labels) to its name,
|
|
203
|
+
as an ASCII-lowercased string.
|
|
204
|
+
|
|
205
|
+
If an encoding with that label does not exist, returns `null`.
|
|
206
|
+
|
|
207
|
+
This is the same as [`decoder.encoding` getter](https://encoding.spec.whatwg.org/#dom-textdecoder-encoding),
|
|
208
|
+
except that it does not throw for invalid labels and instead returns `null`, and is identical to
|
|
209
|
+
the following code:
|
|
210
|
+
```js
|
|
211
|
+
try {
|
|
212
|
+
if (!label) return null // does not default to 'utf-8'
|
|
213
|
+
return new TextDecoder(label).encoding
|
|
214
|
+
} catch {
|
|
215
|
+
return null
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
All encoding names are also valid labels for corresponding encodings.
|
|
220
|
+
|
|
221
|
+
#### `getBOMEncoding(input)`
|
|
222
|
+
|
|
223
|
+
Implements [BOM sniff](https://encoding.spec.whatwg.org/#bom-sniff) legacy hook.
|
|
224
|
+
|
|
225
|
+
Given a `TypedArray` or an `ArrayBuffer` instance `input`, returns either of:
|
|
226
|
+
* `'utf-8'`, if `input` starts with UTF-8 byte order mark.
|
|
227
|
+
* `'utf-16le'`, if `input` starts with UTF-16LE byte order mark.
|
|
228
|
+
* `'utf-16be'`, if `input` starts with UTF-16BE byte order mark.
|
|
229
|
+
* `null` otherwise.
|
|
230
|
+
|
|
231
|
+
#### `legacyHookDecode(input, fallbackEncoding = 'utf-8')`
|
|
232
|
+
|
|
233
|
+
Implements [decode](https://encoding.spec.whatwg.org/#decode) legacy hook.
|
|
234
|
+
|
|
235
|
+
Given a `TypedArray` or an `ArrayBuffer` instance `input` and an optional `fallbackEncoding`
|
|
236
|
+
normalized encoding name, sniffs encoding from BOM with `fallbackEncoding` fallback and then
|
|
237
|
+
decodes the `input` using that encoding, skipping BOM if it was present.
|
|
238
|
+
|
|
239
|
+
Notes:
|
|
240
|
+
|
|
241
|
+
* BOM-sniffed encoding takes precedence over `fallbackEncoding` option per spec.
|
|
242
|
+
Use with care.
|
|
243
|
+
* `fallbackEncoding` must be ASCII-lowercased encoding name,
|
|
244
|
+
e.g. a result of `normalizeEncoding(label)` call.
|
|
245
|
+
* Always operates in non-fatal [mode](https://encoding.spec.whatwg.org/#textdecoder-error-mode),
|
|
246
|
+
aka replacement. It can convert different byte sequences to equal strings.
|
|
247
|
+
|
|
248
|
+
This method is similar to the following code, except that it doesn't support encoding labels and
|
|
249
|
+
only expects lowercased encoding name:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
new TextDecoder(getBOMEncoding(input) ?? fallbackEncoding ?? 'utf-8').decode(input)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### `@exodus/bytes/encoding-lite.js`
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
import { TextDecoder, TextDecoder } from '@exodus/bytes/encoding-lite.js'
|
|
259
|
+
|
|
260
|
+
// Hooks for standards
|
|
261
|
+
import { getBOMEncoding, legacyHookDecode, normalizeEncoding } from '@exodus/bytes/encoding-lite.js'
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The exact same exports as `@exodus/bytes/encoding.js` are also exported as
|
|
265
|
+
`@exodus/bytes/encoding-lite.js`, with the difference that the lite version does not load
|
|
266
|
+
multi-byte `TextDecoder` encodings by default to reduce bundle size 10x.
|
|
267
|
+
|
|
268
|
+
The only affected encodings are: `gbk`, `gb18030`, `big5`, `euc-jp`, `iso-2022-jp`, `shift_jis`
|
|
269
|
+
and their [labels](https://encoding.spec.whatwg.org/#names-and-labels) when used with `TextDecoder`.
|
|
270
|
+
|
|
271
|
+
Legacy single-byte encodingds are loaded by default in both cases.
|
|
272
|
+
|
|
273
|
+
`TextEncoder` and hooks for standards (including `normalizeEncoding`) do not have any behavior
|
|
274
|
+
differences in the lite version and support full range if inputs.
|
|
275
|
+
|
|
276
|
+
To avoid inconsistencies, the exported classes and methods are exactly the same objects.
|
|
277
|
+
|
|
278
|
+
```console
|
|
279
|
+
> lite = require('@exodus/bytes/encoding-lite.js')
|
|
280
|
+
[Module: null prototype] {
|
|
281
|
+
TextDecoder: [class TextDecoder],
|
|
282
|
+
TextEncoder: [class TextEncoder],
|
|
283
|
+
getBOMEncoding: [Function: getBOMEncoding],
|
|
284
|
+
legacyHookDecode: [Function: legacyHookDecode],
|
|
285
|
+
normalizeEncoding: [Function: normalizeEncoding]
|
|
286
|
+
}
|
|
287
|
+
> new lite.TextDecoder('big5').decode(Uint8Array.of(0x25))
|
|
288
|
+
Uncaught:
|
|
289
|
+
Error: Legacy multi-byte encodings are disabled in /encoding-lite.js, use /encoding.js for full encodings range support
|
|
290
|
+
|
|
291
|
+
> full = require('@exodus/bytes/encoding.js')
|
|
292
|
+
[Module: null prototype] {
|
|
293
|
+
TextDecoder: [class TextDecoder],
|
|
294
|
+
TextEncoder: [class TextEncoder],
|
|
295
|
+
getBOMEncoding: [Function: getBOMEncoding],
|
|
296
|
+
legacyHookDecode: [Function: legacyHookDecode],
|
|
297
|
+
normalizeEncoding: [Function: normalizeEncoding]
|
|
298
|
+
}
|
|
299
|
+
> full.TextDecoder === lite.TextDecoder
|
|
300
|
+
true
|
|
301
|
+
> new full.TextDecoder('big5').decode(Uint8Array.of(0x25))
|
|
302
|
+
'%'
|
|
303
|
+
> new lite.TextDecoder('big5').decode(Uint8Array.of(0x25))
|
|
304
|
+
'%'
|
|
305
|
+
```
|
|
306
|
+
|
|
25
307
|
## License
|
|
26
308
|
|
|
27
309
|
[MIT](./LICENSE)
|
package/array.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
// >= TypeScript 5.9 made Uint8Array templated with <> and defaulted to ArrayBufferLike
|
|
4
|
+
// which would incorrectly accept SharedArrayBuffer instances.
|
|
5
|
+
// < TypeScript 5.7 doesn't support templates for Uint8Array.
|
|
6
|
+
// So this type is defined as a workaround to evaluate to Uint8Array<ArrayBuffer> on all versions of TypeScript.
|
|
7
|
+
export type Uint8ArrayBuffer = ReturnType<typeof Uint8Array.from>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Output format for typed array conversions
|
|
11
|
+
*/
|
|
12
|
+
export type OutputFormat = 'uint8' | 'buffer';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a view of a TypedArray in the specified format
|
|
16
|
+
* Note: This does not copy data - returns a view on the same underlying buffer
|
|
17
|
+
* @param arr - The input TypedArray
|
|
18
|
+
* @param format - The desired output format ('uint8' or 'buffer')
|
|
19
|
+
* @returns A view on the same underlying buffer
|
|
20
|
+
*/
|
|
21
|
+
export function typedView(arr: ArrayBufferView, format: 'uint8'): Uint8Array;
|
|
22
|
+
export function typedView(arr: ArrayBufferView, format: 'buffer'): Buffer;
|
|
23
|
+
export function typedView(arr: ArrayBufferView, format: OutputFormat): Uint8Array | Buffer;
|
|
24
|
+
|
package/base58.js
CHANGED
|
@@ -3,10 +3,10 @@ import { assertUint8 } from './assert.js'
|
|
|
3
3
|
import { nativeDecoder, nativeEncoder, isHermes } from './fallback/_utils.js'
|
|
4
4
|
import { encodeAscii, decodeAscii } from './fallback/latin1.js'
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
6
|
+
const alphabet58 = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
|
|
7
|
+
const alphabetXRP = [...'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz']
|
|
8
|
+
const codes58 = new Uint8Array(alphabet58.map((x) => x.charCodeAt(0)))
|
|
9
|
+
const codesXRP = new Uint8Array(alphabetXRP.map((x) => x.charCodeAt(0)))
|
|
10
10
|
|
|
11
11
|
const _0n = BigInt(0)
|
|
12
12
|
const _1n = BigInt(1)
|
|
@@ -16,17 +16,18 @@ const _58n = BigInt(58)
|
|
|
16
16
|
const _0xffffffffn = BigInt(0xff_ff_ff_ff)
|
|
17
17
|
|
|
18
18
|
let table // 15 * 82, diagonal, <1kb
|
|
19
|
-
|
|
19
|
+
const fromMaps = new Map()
|
|
20
20
|
|
|
21
21
|
const E_CHAR = 'Invalid character in base58 input'
|
|
22
22
|
|
|
23
23
|
const shouldUseBigIntFrom = isHermes // faster only on Hermes, numbers path beats it on normal engines
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
function toBase58core(arr, alphabet, codes) {
|
|
26
26
|
assertUint8(arr)
|
|
27
27
|
const length = arr.length
|
|
28
28
|
if (length === 0) return ''
|
|
29
29
|
|
|
30
|
+
const ZERO = alphabet[0]
|
|
30
31
|
let zeros = 0
|
|
31
32
|
while (zeros < length && arr[zeros] === 0) zeros++
|
|
32
33
|
|
|
@@ -120,18 +121,20 @@ export function toBase58(arr) {
|
|
|
120
121
|
return ZERO.repeat(zeros) + out
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
export function fromBase58(str, format = 'uint8') {
|
|
124
|
+
function fromBase58core(str, alphabet, codes, format = 'uint8') {
|
|
125
125
|
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
126
126
|
const length = str.length
|
|
127
127
|
if (length === 0) return typedView(new Uint8Array(), format)
|
|
128
128
|
|
|
129
|
+
const zeroC = codes[0]
|
|
129
130
|
let zeros = 0
|
|
130
131
|
while (zeros < length && str.charCodeAt(zeros) === zeroC) zeros++
|
|
131
132
|
|
|
133
|
+
let fromMap = fromMaps.get(alphabet)
|
|
132
134
|
if (!fromMap) {
|
|
133
135
|
fromMap = new Int8Array(256).fill(-1)
|
|
134
136
|
for (let i = 0; i < 58; i++) fromMap[alphabet[i].charCodeAt(0)] = i
|
|
137
|
+
fromMaps.set(alphabet, fromMap)
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
const size = zeros + (((length - zeros + 1) * 3) >> 2) // 3/4 rounded up, larger than ~0.73 coef to fit everything
|
|
@@ -210,3 +213,8 @@ export function fromBase58(str, format = 'uint8') {
|
|
|
210
213
|
|
|
211
214
|
return typedView(res.slice(at - zeros), format) // slice is faster for small sizes than subarray
|
|
212
215
|
}
|
|
216
|
+
|
|
217
|
+
export const toBase58 = (arr) => toBase58core(arr, alphabet58, codes58)
|
|
218
|
+
export const fromBase58 = (str, format) => fromBase58core(str, alphabet58, codes58, format)
|
|
219
|
+
export const toBase58xrp = (arr) => toBase58core(arr, alphabetXRP, codesXRP)
|
|
220
|
+
export const fromBase58xrp = (str, format) => fromBase58core(str, alphabetXRP, codesXRP, format)
|
package/base64.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
|
|
3
|
+
import type { OutputFormat, Uint8ArrayBuffer } from './array.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for base64 encoding
|
|
7
|
+
*/
|
|
8
|
+
export interface ToBase64Options {
|
|
9
|
+
/** Whether to include padding characters (default: true for base64, false for base64url) */
|
|
10
|
+
padding?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Padding mode for base64 decoding
|
|
15
|
+
* - true: padding is required
|
|
16
|
+
* - false: padding is not allowed
|
|
17
|
+
* - 'both': padding is optional (default for base64)
|
|
18
|
+
*/
|
|
19
|
+
export type PaddingMode = boolean | 'both';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for base64 decoding
|
|
23
|
+
*/
|
|
24
|
+
export interface FromBase64Options {
|
|
25
|
+
/** Output format (default: 'uint8') */
|
|
26
|
+
format?: OutputFormat;
|
|
27
|
+
/** Padding mode */
|
|
28
|
+
padding?: PaddingMode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Encodes a Uint8Array to a base64 string (RFC 4648)
|
|
33
|
+
* @param arr - The input bytes
|
|
34
|
+
* @param options - Encoding options
|
|
35
|
+
* @returns The base64 encoded string
|
|
36
|
+
*/
|
|
37
|
+
export function toBase64(arr: Uint8ArrayBuffer, options?: ToBase64Options): string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Encodes a Uint8Array to a base64url string (RFC 4648)
|
|
41
|
+
* @param arr - The input bytes
|
|
42
|
+
* @param options - Encoding options (padding defaults to false)
|
|
43
|
+
* @returns The base64url encoded string
|
|
44
|
+
*/
|
|
45
|
+
export function toBase64url(arr: Uint8ArrayBuffer, options?: ToBase64Options): string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Decodes a base64 string to bytes
|
|
49
|
+
* Operates in strict mode for last chunk, does not allow whitespace
|
|
50
|
+
* @param str - The base64 encoded string
|
|
51
|
+
* @param options - Decoding options
|
|
52
|
+
* @returns The decoded bytes
|
|
53
|
+
*/
|
|
54
|
+
export function fromBase64(str: string, options?: FromBase64Options): Uint8ArrayBuffer;
|
|
55
|
+
export function fromBase64(str: string, options: FromBase64Options & { format: 'buffer' }): Buffer;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Decodes a base64url string to bytes
|
|
59
|
+
* Operates in strict mode for last chunk, does not allow whitespace
|
|
60
|
+
* @param str - The base64url encoded string
|
|
61
|
+
* @param options - Decoding options (padding defaults to false)
|
|
62
|
+
* @returns The decoded bytes
|
|
63
|
+
*/
|
|
64
|
+
export function fromBase64url(str: string, options?: FromBase64Options): Uint8ArrayBuffer;
|
|
65
|
+
export function fromBase64url(str: string, options: FromBase64Options & { format: 'buffer' }): Buffer;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decodes either base64 or base64url string to bytes
|
|
69
|
+
* Automatically detects the variant based on characters present
|
|
70
|
+
* @param str - The base64 or base64url encoded string
|
|
71
|
+
* @param options - Decoding options
|
|
72
|
+
* @returns The decoded bytes
|
|
73
|
+
*/
|
|
74
|
+
export function fromBase64any(str: string, options?: FromBase64Options): Uint8ArrayBuffer;
|
|
75
|
+
export function fromBase64any(str: string, options: FromBase64Options & { format: 'buffer' }): Buffer;
|
|
76
|
+
|
package/bigint.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { toHex, fromHex } from '@exodus/bytes/hex.js'
|
|
2
|
+
import { assert } from './fallback/_utils.js'
|
|
3
|
+
|
|
4
|
+
const _0n = BigInt(0)
|
|
5
|
+
|
|
6
|
+
export function fromBigInt(x, { length, format } = {}) {
|
|
7
|
+
assert(Number.isSafeInteger(length) && length > 0, 'Expected length arg to be a positive integer')
|
|
8
|
+
assert(typeof x === 'bigint' && x >= _0n, 'Expected a non-negative bigint')
|
|
9
|
+
const hex = x.toString(16)
|
|
10
|
+
assert(length * 2 >= hex.length, `Can not fit supplied number into ${length} bytes`)
|
|
11
|
+
return fromHex(hex.padStart(length * 2, '0'), format)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const toBigInt = (a) => BigInt('0x' + (toHex(a) || '0'))
|
package/encoding-lite.js
ADDED
package/encoding.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createMultibyteDecoder } from '@exodus/bytes/multi-byte.js' // eslint-disable-line @exodus/import/no-unresolved
|
|
2
|
+
import { setMultibyteDecoder } from './fallback/encoding.js'
|
|
3
|
+
|
|
4
|
+
setMultibyteDecoder(createMultibyteDecoder)
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
TextDecoder,
|
|
8
|
+
TextEncoder,
|
|
9
|
+
normalizeEncoding,
|
|
10
|
+
getBOMEncoding,
|
|
11
|
+
legacyHookDecode,
|
|
12
|
+
} from './fallback/encoding.js'
|
package/fallback/_utils.js
CHANGED
|
@@ -1,17 +1,38 @@
|
|
|
1
1
|
const { Buffer, TextEncoder, TextDecoder } = globalThis
|
|
2
2
|
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
3
|
+
export const nativeBuffer = haveNativeBuffer ? Buffer : null
|
|
4
|
+
export const isHermes = Boolean(globalThis.HermesInternal)
|
|
5
|
+
export const isDeno = Boolean(globalThis.Deno)
|
|
6
|
+
export const isLE = new Uint8Array(Uint16Array.of(258).buffer)[0] === 2
|
|
7
|
+
|
|
8
|
+
let isNative = (x) => {
|
|
9
|
+
if (!x) return false
|
|
10
|
+
if (haveNativeBuffer) return true // we consider Node.js TextDecoder/TextEncoder native
|
|
11
|
+
const s = `${x}`
|
|
12
|
+
// See https://github.com/facebook/hermes/pull/1855#issuecomment-3659386410
|
|
13
|
+
return s.includes('[native code]') || s.includes(`[bytecode]`) // Static Hermes has [bytecode] for contrib, which includes TextEncoder/TextDecoder
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!haveNativeBuffer && isNative(() => {})) isNative = () => false // e.g. XS, we don't want false positives
|
|
17
|
+
|
|
18
|
+
export const nativeEncoder = isNative(TextEncoder) ? new TextEncoder() : null
|
|
19
|
+
export const nativeDecoder = isNative(TextDecoder)
|
|
20
|
+
? new TextDecoder('utf-8', { ignoreBOM: true })
|
|
21
|
+
: null
|
|
8
22
|
|
|
9
23
|
// Actually windows-1252, compatible with ascii and latin1 decoding
|
|
10
24
|
// Beware that on non-latin1, i.e. on windows-1252, this is broken in ~all Node.js versions released
|
|
11
25
|
// in 2025 due to a regression, so we call it Latin1 as it's usable only for that
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
26
|
+
let nativeDecoderLatin1impl = null
|
|
27
|
+
if (nativeDecoder) {
|
|
28
|
+
// Not all barebone engines with TextDecoder support something except utf-8, detect
|
|
29
|
+
try {
|
|
30
|
+
nativeDecoderLatin1impl = new TextDecoder('latin1', { ignoreBOM: true })
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const nativeDecoderLatin1 = nativeDecoderLatin1impl
|
|
35
|
+
export const canDecoders = Boolean(nativeDecoderLatin1impl)
|
|
15
36
|
|
|
16
37
|
// Block Firefox < 146 specifically from using native hex/base64, as it's very slow there
|
|
17
38
|
// Refs: https://bugzilla.mozilla.org/show_bug.cgi?id=1994067 (and linked issues), fixed in 146
|
|
@@ -35,6 +56,75 @@ function shouldSkipBuiltins() {
|
|
|
35
56
|
return false // eslint-disable-line no-unreachable
|
|
36
57
|
}
|
|
37
58
|
|
|
38
|
-
const skipWeb = shouldSkipBuiltins()
|
|
59
|
+
export const skipWeb = shouldSkipBuiltins()
|
|
39
60
|
|
|
40
|
-
|
|
61
|
+
function decodePartAddition(a, start, end, m) {
|
|
62
|
+
let o = ''
|
|
63
|
+
let i = start
|
|
64
|
+
for (const last3 = end - 3; i < last3; i += 4) {
|
|
65
|
+
const x0 = a[i]
|
|
66
|
+
const x1 = a[i + 1]
|
|
67
|
+
const x2 = a[i + 2]
|
|
68
|
+
const x3 = a[i + 3]
|
|
69
|
+
o += m[x0]
|
|
70
|
+
o += m[x1]
|
|
71
|
+
o += m[x2]
|
|
72
|
+
o += m[x3]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
while (i < end) o += m[a[i++]]
|
|
76
|
+
return o
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Decoding with templates is faster on Hermes
|
|
80
|
+
function decodePartTemplates(a, start, end, m) {
|
|
81
|
+
let o = ''
|
|
82
|
+
let i = start
|
|
83
|
+
for (const last15 = end - 15; i < last15; i += 16) {
|
|
84
|
+
const x0 = a[i]
|
|
85
|
+
const x1 = a[i + 1]
|
|
86
|
+
const x2 = a[i + 2]
|
|
87
|
+
const x3 = a[i + 3]
|
|
88
|
+
const x4 = a[i + 4]
|
|
89
|
+
const x5 = a[i + 5]
|
|
90
|
+
const x6 = a[i + 6]
|
|
91
|
+
const x7 = a[i + 7]
|
|
92
|
+
const x8 = a[i + 8]
|
|
93
|
+
const x9 = a[i + 9]
|
|
94
|
+
const x10 = a[i + 10]
|
|
95
|
+
const x11 = a[i + 11]
|
|
96
|
+
const x12 = a[i + 12]
|
|
97
|
+
const x13 = a[i + 13]
|
|
98
|
+
const x14 = a[i + 14]
|
|
99
|
+
const x15 = a[i + 15]
|
|
100
|
+
o += `${m[x0]}${m[x1]}${m[x2]}${m[x3]}${m[x4]}${m[x5]}${m[x6]}${m[x7]}${m[x8]}${m[x9]}${m[x10]}${m[x11]}${m[x12]}${m[x13]}${m[x14]}${m[x15]}`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
while (i < end) o += m[a[i++]]
|
|
104
|
+
return o
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const decodePart = isHermes ? decodePartTemplates : decodePartAddition
|
|
108
|
+
export function decode2string(arr, start, end, m) {
|
|
109
|
+
if (start - end > 30_000) {
|
|
110
|
+
// Limit concatenation to avoid excessive GC
|
|
111
|
+
// Thresholds checked on Hermes for toHex
|
|
112
|
+
const concat = []
|
|
113
|
+
for (let i = start; i < end; ) {
|
|
114
|
+
const step = i + 500
|
|
115
|
+
const iNext = step > end ? end : step
|
|
116
|
+
concat.push(decodePart(arr, i, iNext, m))
|
|
117
|
+
i = iNext
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const res = concat.join('')
|
|
121
|
+
concat.length = 0
|
|
122
|
+
return res
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return decodePart(arr, start, end, m)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function assert(condition, msg) {
|
|
129
|
+
if (!condition) throw new Error(msg)
|
|
130
|
+
}
|