@exodus/bytes 1.0.0-rc.1 → 1.0.0-rc.11

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/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@exodus/bytes",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0-rc.11",
4
4
  "description": "Various operations on Uint8Array data",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
7
- "test:v8": "npm run test:d8 --",
8
7
  "test:javascriptcore": "npm run test:jsc --",
9
- "test:d8": "exodus-test --engine=d8:bundle",
8
+ "test:v8": "exodus-test --engine=v8:bundle",
10
9
  "test:jsc": "exodus-test --engine=jsc:bundle",
11
10
  "test:spidermonkey": "exodus-test --engine=spidermonkey:bundle",
12
11
  "test:hermes": "exodus-test --engine=hermes:bundle",
13
12
  "test:quickjs": "exodus-test --engine=quickjs:bundle",
13
+ "test:xs": "exodus-test --engine=xs:bundle",
14
+ "test:engine262": "exodus-test --engine=engine262:bundle",
15
+ "test:deno": "exodus-test --engine=deno:pure",
16
+ "test:bun": "exodus-test --engine=bun:pure",
14
17
  "test:electron:bundle": "exodus-test --engine=electron:bundle",
15
18
  "test:electron:as-node": "exodus-test --engine=electron-as-node:test",
16
19
  "test:chrome:puppeteer": "exodus-test --engine=chrome:puppeteer",
@@ -18,6 +21,7 @@
18
21
  "test:webkit:playwright": "exodus-test --engine=webkit:playwright",
19
22
  "test:firefox:puppeteer": "exodus-test --engine=firefox:puppeteer",
20
23
  "test:firefox:playwright": "exodus-test --engine=firefox:playwright",
24
+ "test:servo:bundle": "exodus-test --engine=servo:bundle",
21
25
  "test": "exodus-test",
22
26
  "jsvu": "jsvu",
23
27
  "playwright": "exodus-test --playwright",
@@ -26,43 +30,131 @@
26
30
  },
27
31
  "repository": {
28
32
  "type": "git",
29
- "url": "git+https://github.com/ExodusMovement/bytes.git"
33
+ "url": "git+https://github.com/ExodusOSS/bytes.git"
30
34
  },
31
35
  "author": "Exodus Movement, Inc.",
32
36
  "license": "MIT",
33
37
  "bugs": {
34
- "url": "https://github.com/ExodusMovement/bytes/issues"
38
+ "url": "https://github.com/ExodusOSS/bytes/issues"
35
39
  },
36
- "homepage": "https://github.com/ExodusMovement/bytes#readme",
40
+ "homepage": "https://github.com/ExodusOSS/bytes#readme",
37
41
  "engines": {
38
42
  "node": "^20.19.0 || >=22.13.0"
39
43
  },
40
44
  "type": "module",
41
45
  "files": [
46
+ "/fallback/_utils.js",
47
+ "/fallback/base32.js",
48
+ "/fallback/base64.js",
49
+ "/fallback/encoding.js",
50
+ "/fallback/encoding.labels.js",
51
+ "/fallback/encoding.util.js",
52
+ "/fallback/hex.js",
53
+ "/fallback/latin1.js",
54
+ "/fallback/multi-byte.encodings.cjs",
55
+ "/fallback/multi-byte.encodings.json",
56
+ "/fallback/multi-byte.js",
57
+ "/fallback/multi-byte.table.js",
58
+ "/fallback/single-byte.encodings.js",
59
+ "/fallback/single-byte.js",
60
+ "/fallback/utf16.js",
61
+ "/fallback/utf8.js",
62
+ "/array.js",
42
63
  "/assert.js",
64
+ "/base32.js",
65
+ "/base58.js",
66
+ "/base58check.js",
43
67
  "/base64.js",
44
- "/array.js",
45
- "/hex.js"
68
+ "/bech32.js",
69
+ "/encoding.js",
70
+ "/encoding-lite.js",
71
+ "/hex.js",
72
+ "/hex.node.js",
73
+ "/multi-byte.js",
74
+ "/multi-byte.node.js",
75
+ "/single-byte.js",
76
+ "/single-byte.node.js",
77
+ "/utf16.js",
78
+ "/utf16.node.js",
79
+ "/utf8.js",
80
+ "/utf8.node.js",
81
+ "/wif.js"
46
82
  ],
47
83
  "exports": {
48
- "./base64.js": "./base64.js",
49
84
  "./array.js": "./array.js",
50
- "./hex.js": "./hex.js"
85
+ "./base32.js": "./base32.js",
86
+ "./base58.js": "./base58.js",
87
+ "./base58check.js": "./base58check.js",
88
+ "./base64.js": "./base64.js",
89
+ "./bech32.js": "./bech32.js",
90
+ "./hex.js": {
91
+ "node": "./hex.node.js",
92
+ "default": "./hex.js"
93
+ },
94
+ "./multi-byte.js": {
95
+ "node": "./multi-byte.node.js",
96
+ "default": "./multi-byte.js"
97
+ },
98
+ "./single-byte.js": {
99
+ "node": "./single-byte.node.js",
100
+ "default": "./single-byte.js"
101
+ },
102
+ "./encoding.js": "./encoding.js",
103
+ "./encoding-lite.js": "./encoding-lite.js",
104
+ "./utf16.js": {
105
+ "node": "./utf16.node.js",
106
+ "default": "./utf16.js"
107
+ },
108
+ "./utf8.js": {
109
+ "node": "./utf8.node.js",
110
+ "default": "./utf8.js"
111
+ },
112
+ "./wif.js": "./wif.js"
113
+ },
114
+ "peerDependencies": {
115
+ "@exodus/crypto": "^1.0.0-rc.4"
116
+ },
117
+ "peerDependenciesMeta": {
118
+ "@exodus/crypto": {
119
+ "optional": true
120
+ }
51
121
  },
52
- "dependencies": {},
53
122
  "devDependencies": {
123
+ "@ethersproject/strings": "^5.8.0",
124
+ "@exodus/crypto": "^1.0.0-rc.30",
54
125
  "@exodus/eslint-config": "^5.24.0",
55
126
  "@exodus/prettier": "^1.0.0",
56
- "@exodus/test": "^1.0.0-rc.105",
127
+ "@exodus/test": "^1.0.0-rc.109",
128
+ "@noble/hashes": "^2.0.1",
57
129
  "@scure/base": "^1.2.6",
58
- "@types/node": "^24.0.10",
130
+ "@stablelib/base64": "^2.0.1",
131
+ "@stablelib/hex": "^2.0.1",
132
+ "@types/node": "^22.13.0",
59
133
  "base-x": "^5.0.1",
60
134
  "base32.js": "^0.1.0",
135
+ "base58-js": "^3.0.3",
61
136
  "base64-js": "^1.5.1",
137
+ "bech32": "^2.0.0",
138
+ "bs58": "^6.0.0",
139
+ "bs58check": "^4.0.0",
140
+ "bstring": "^0.3.9",
62
141
  "buffer": "^6.0.3",
142
+ "decode-utf8": "^1.0.1",
63
143
  "electron": "36.5.0",
144
+ "encode-utf8": "^2.0.0",
64
145
  "eslint": "^8.44.0",
65
- "jsvu": "^3.0.0"
146
+ "fast-base64-decode": "^2.0.0",
147
+ "fast-base64-encode": "^1.0.0",
148
+ "hextreme": "^1.0.7",
149
+ "hi-base32": "^0.5.1",
150
+ "iconv-lite": "^0.7.0",
151
+ "jsvu": "^3.0.3",
152
+ "text-encoding": "^0.7.0",
153
+ "typescript": "^5.9.3",
154
+ "uint8array-tools": "^0.0.9",
155
+ "utf8": "^3.0.0",
156
+ "whatwg-encoding": "^3.1.1",
157
+ "wif": "^5.0.0"
66
158
  },
67
159
  "prettier": "@exodus/prettier",
68
160
  "packageManager": "pnpm@10.12.1+sha256.889bac470ec93ccc3764488a19d6ba8f9c648ad5e50a9a6e4be3768a5de387a3"
package/single-byte.js ADDED
@@ -0,0 +1,55 @@
1
+ import { assertUint8 } from './assert.js'
2
+ import { canDecoders } from './fallback/_utils.js'
3
+ import { assertEncoding, encodingDecoder } from './fallback/single-byte.js'
4
+
5
+ const { TextDecoder } = globalThis
6
+
7
+ let windows1252works
8
+
9
+ function shouldUseNative(enc) {
10
+ // https://issues.chromium.org/issues/468458388
11
+ // Also might be incorrectly imlemented on platforms as Latin1 (e.g. in Node.js) or regress
12
+ // This is the most significant single-byte encoding, 'ascii' and 'latin1' alias to this
13
+ // Even after Chrome bug is fixed, this should serve as a quick correctness check that it's actually windows-1252
14
+ if (enc === 'windows-1252') {
15
+ if (windows1252works === undefined) {
16
+ windows1252works = false
17
+ try {
18
+ const u = new Uint8Array(9) // using 9 bytes is significant to catch the bug
19
+ u[8] = 128
20
+ windows1252works = new TextDecoder(enc).decode(u).codePointAt(8) === 0x20_ac
21
+ } catch {}
22
+ }
23
+
24
+ return windows1252works
25
+ }
26
+
27
+ // iso-8859-16 is somehow broken in WebKit, at least on CI
28
+ return enc !== 'iso-8859-16'
29
+ }
30
+
31
+ export function createSinglebyteDecoder(encoding, loose = false) {
32
+ if (encoding === 'iso-8859-8-i') encoding = 'iso-8859-8'
33
+ assertEncoding(encoding)
34
+
35
+ if (canDecoders && shouldUseNative(encoding)) {
36
+ // In try, as not all encodings might be implemented in all engines which have native TextDecoder
37
+ try {
38
+ const decoder = new TextDecoder(encoding, { fatal: !loose })
39
+ return (arr) => {
40
+ assertUint8(arr)
41
+ if (arr.byteLength === 0) return ''
42
+ return decoder.decode(arr)
43
+ }
44
+ } catch {}
45
+ }
46
+
47
+ const jsDecoder = encodingDecoder(encoding)
48
+ return (arr) => {
49
+ assertUint8(arr)
50
+ if (arr.byteLength === 0) return ''
51
+ return jsDecoder(arr, loose)
52
+ }
53
+ }
54
+
55
+ export const windows1252toString = createSinglebyteDecoder('windows-1252')
@@ -0,0 +1,62 @@
1
+ import { assertUint8 } from './assert.js'
2
+ import { isAscii } from 'node:buffer'
3
+ import { isDeno, isLE } from './fallback/_utils.js'
4
+ import { asciiPrefix, decodeLatin1 } from './fallback/latin1.js'
5
+ import { encodingMapper, encodingDecoder, E_STRICT } from './fallback/single-byte.js'
6
+
7
+ const toBuf = (x) => Buffer.from(x.buffer, x.byteOffset, x.byteLength)
8
+
9
+ function latin1Prefix(arr, start) {
10
+ let p = start | 0
11
+ const length = arr.length
12
+ for (const len3 = length - 3; p < len3; p += 4) {
13
+ if ((arr[p] & 0xe0) === 0x80) return p
14
+ if ((arr[p + 1] & 0xe0) === 0x80) return p + 1
15
+ if ((arr[p + 2] & 0xe0) === 0x80) return p + 2
16
+ if ((arr[p + 3] & 0xe0) === 0x80) return p + 3
17
+ }
18
+
19
+ for (; p < length; p++) {
20
+ if ((arr[p] & 0xe0) === 0x80) return p
21
+ }
22
+
23
+ return length
24
+ }
25
+
26
+ export function createSinglebyteDecoder(encoding, loose = false) {
27
+ if (encoding === 'iso-8859-8-i') encoding = 'iso-8859-8'
28
+ const latin1path = encoding === 'windows-1252' // TODO: are there more?
29
+ if (isDeno) {
30
+ const jsDecoder = encodingDecoder(encoding) // asserts
31
+ return (arr) => {
32
+ assertUint8(arr)
33
+ if (arr.byteLength === 0) return ''
34
+ if (isAscii(arr)) return toBuf(arr).toString()
35
+ return jsDecoder(arr, loose) // somewhy faster on Deno anyway, TODO: optimize?
36
+ }
37
+ }
38
+
39
+ const { incomplete, mapper } = encodingMapper(encoding) // asserts
40
+ return (arr) => {
41
+ assertUint8(arr)
42
+ if (arr.byteLength === 0) return ''
43
+ if (isAscii(arr)) return toBuf(arr).latin1Slice(0, arr.byteLength) // .latin1Slice is faster than .asciiSlice
44
+
45
+ // Node.js TextDecoder is broken, so we can't use it. It's also slow anyway
46
+
47
+ let prefixBytes = asciiPrefix(arr)
48
+ let prefix = ''
49
+ if (latin1path) prefixBytes = latin1Prefix(arr, prefixBytes)
50
+ if (prefixBytes > 64 || prefixBytes === arr.length) {
51
+ prefix = toBuf(arr).latin1Slice(0, prefixBytes) // .latin1Slice is faster than .asciiSlice
52
+ if (prefixBytes === arr.length) return prefix
53
+ }
54
+
55
+ const b = mapper(arr, prefix.length) // prefix.length can mismatch prefixBytes
56
+ const suffix = isLE ? toBuf(b).ucs2Slice(0, b.byteLength) : decodeLatin1(b, 0, b.length) // decodeLatin1 is actually capable of decoding 16-bit codepoints
57
+ if (!loose && incomplete && suffix.includes('\uFFFD')) throw new TypeError(E_STRICT)
58
+ return prefix + suffix
59
+ }
60
+ }
61
+
62
+ export const windows1252toString = createSinglebyteDecoder('windows-1252')
package/utf16.js ADDED
@@ -0,0 +1,73 @@
1
+ import * as js from './fallback/utf16.js'
2
+ import { canDecoders, isLE } from './fallback/_utils.js'
3
+
4
+ const { TextDecoder } = globalThis // Buffer is optional
5
+ const ignoreBOM = true
6
+ const decoderFatalLE = canDecoders ? new TextDecoder('utf-16le', { ignoreBOM, fatal: true }) : null
7
+ const decoderLooseLE = canDecoders ? new TextDecoder('utf-16le', { ignoreBOM }) : null
8
+ const decoderFatalBE = canDecoders ? new TextDecoder('utf-16be', { ignoreBOM, fatal: true }) : null
9
+ const decoderLooseBE = canDecoders ? new TextDecoder('utf-16be', { ignoreBOM }) : null
10
+ const decoderFatal16 = isLE ? decoderFatalLE : decoderFatalBE
11
+ const decoderLoose16 = isLE ? decoderLooseLE : decoderFatalBE
12
+ const { isWellFormed } = String.prototype
13
+
14
+ const { E_STRICT, E_STRICT_UNICODE } = js
15
+
16
+ // Unlike utf8, operates on Uint16Arrays by default
17
+
18
+ const to8 = (a) => new Uint8Array(a.buffer, a.byteOffset, a.byteLength)
19
+
20
+ function encode(str, loose = false, format = 'uint16') {
21
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
22
+ if (format !== 'uint16' && format !== 'uint8-le' && format !== 'uint8-be') {
23
+ throw new TypeError('Unknown format')
24
+ }
25
+
26
+ const shouldSwap = (isLE && format === 'uint8-be') || (!isLE && format === 'uint8-le')
27
+
28
+ // On v8 and SpiderMonkey, check via isWellFormed is faster than js
29
+ // On JSC, check during loop is faster than isWellFormed
30
+ // If isWellFormed is available, we skip check during decoding and recheck after
31
+ // If isWellFormed is unavailable, we check in js during decoding
32
+ if (!loose && isWellFormed && !isWellFormed.call(str)) throw new TypeError(E_STRICT_UNICODE)
33
+ const u16 = js.encode(str, loose, !loose && isWellFormed, shouldSwap)
34
+
35
+ if (format === 'uint8-le' || format === 'uint8-be') return to8(u16) // Already swapped
36
+ if (format === 'uint16') return u16
37
+ throw new Error('Unreachable')
38
+ }
39
+
40
+ function decode(input, loose = false, format = 'uint16') {
41
+ let u16
42
+ switch (format) {
43
+ case 'uint16':
44
+ if (!(input instanceof Uint16Array)) throw new TypeError('Expected an Uint16Array')
45
+ if (canDecoders) return loose ? decoderLoose16.decode(input) : decoderFatal16.decode(input)
46
+ u16 = input
47
+ break
48
+ case 'uint8-le':
49
+ if (!(input instanceof Uint8Array)) throw new TypeError('Expected an Uint8Array')
50
+ if (input.byteLength % 2 !== 0) throw new TypeError('Expected even number of bytes')
51
+ if (canDecoders) return loose ? decoderLooseLE.decode(input) : decoderFatalLE.decode(input)
52
+ u16 = js.to16input(input, true)
53
+ break
54
+ case 'uint8-be':
55
+ if (!(input instanceof Uint8Array)) throw new TypeError('Expected an Uint8Array')
56
+ if (input.byteLength % 2 !== 0) throw new TypeError('Expected even number of bytes')
57
+ if (canDecoders) return loose ? decoderLooseBE.decode(input) : decoderFatalBE.decode(input)
58
+ u16 = js.to16input(input, false)
59
+ break
60
+ default:
61
+ throw new TypeError('Unknown format')
62
+ }
63
+
64
+ const str = js.decode(u16, loose, !loose && isWellFormed)
65
+ if (!loose && isWellFormed && !isWellFormed.call(str)) throw new TypeError(E_STRICT)
66
+
67
+ return str
68
+ }
69
+
70
+ export const utf16fromString = (str, format = 'uint16') => encode(str, false, format)
71
+ export const utf16fromStringLoose = (str, format = 'uint16') => encode(str, true, format)
72
+ export const utf16toString = (arr, format = 'uint16') => decode(arr, false, format)
73
+ export const utf16toStringLoose = (arr, format = 'uint16') => decode(arr, true, format)
package/utf16.node.js ADDED
@@ -0,0 +1,79 @@
1
+ import { nativeDecoder, isDeno, isLE } from './fallback/_utils.js'
2
+ import { E_STRICT, E_STRICT_UNICODE } from './fallback/utf16.js'
3
+
4
+ if (Buffer.TYPED_ARRAY_SUPPORT) throw new Error('Unexpected Buffer polyfill')
5
+
6
+ const { isWellFormed } = String.prototype
7
+ const to8 = (a) => new Uint8Array(a.buffer, a.byteOffset, a.byteLength)
8
+
9
+ // Unlike utf8, operates on Uint16Arrays by default
10
+
11
+ function encode(str, loose = false, format = 'uint16') {
12
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
13
+ if (format !== 'uint16' && format !== 'uint8-le' && format !== 'uint8-be') {
14
+ throw new TypeError('Unknown format')
15
+ }
16
+
17
+ if (!isWellFormed.call(str)) {
18
+ if (!loose) throw new TypeError(E_STRICT_UNICODE)
19
+ str = nativeDecoder.decode(Buffer.from(str)) // well, let's fix up (Buffer doesn't do this with utf16 encoding)
20
+ }
21
+
22
+ const ble = Buffer.from(str, 'utf-16le')
23
+
24
+ if (format === 'uint8-le') return to8(ble)
25
+ if (format === 'uint8-be') return to8(ble.swap16())
26
+ if (format === 'uint16') {
27
+ const b = ble.byteOffset % 2 === 0 ? ble : Buffer.from(ble) // it should be already aligned, but just in case
28
+ if (!isLE) b.swap16()
29
+ return new Uint16Array(b.buffer, b.byteOffset, b.byteLength / 2)
30
+ }
31
+
32
+ throw new Error('Unreachable')
33
+ }
34
+
35
+ const swapped = (x, swap) =>
36
+ swap ? Buffer.from(x).swap16() : Buffer.from(x.buffer, x.byteOffset, x.byteLength)
37
+
38
+ // We skip TextDecoder on Node.js, as it's is somewhy significantly slower than Buffer for utf16
39
+ function decodeNode(input, loose = false, format = 'uint16') {
40
+ let ble
41
+ if (format === 'uint16') {
42
+ if (!(input instanceof Uint16Array)) throw new TypeError('Expected an Uint16Array')
43
+ ble = swapped(input, !isLE)
44
+ } else if (format === 'uint8-le' || format === 'uint8-be') {
45
+ if (!(input instanceof Uint8Array)) throw new TypeError('Expected an Uint8Array')
46
+ if (input.byteLength % 2 !== 0) throw new TypeError('Expected even number of bytes')
47
+ ble = swapped(input, format === 'uint8-be')
48
+ } else {
49
+ throw new TypeError('Unknown format')
50
+ }
51
+
52
+ const str = ble.ucs2Slice(0, ble.byteLength)
53
+ if (isWellFormed.call(str)) return str
54
+ if (!loose) throw new TypeError(E_STRICT)
55
+ return nativeDecoder.decode(Buffer.from(str)) // fixup (see above)
56
+ }
57
+
58
+ function decodeDecoder(input, loose = false, format = 'uint16') {
59
+ let encoding
60
+ if (format === 'uint16') {
61
+ if (!(input instanceof Uint16Array)) throw new TypeError('Expected an Uint16Array')
62
+ encoding = isLE ? 'utf-16le' : 'utf-16be'
63
+ } else if (format === 'uint8-le' || format === 'uint8-be') {
64
+ if (!(input instanceof Uint8Array)) throw new TypeError('Expected an Uint8Array')
65
+ if (input.byteLength % 2 !== 0) throw new TypeError('Expected even number of bytes')
66
+ encoding = format === 'uint8-le' ? 'utf-16le' : 'utf-16be'
67
+ } else {
68
+ throw new TypeError('Unknown format')
69
+ }
70
+
71
+ return new TextDecoder(encoding, { ignoreBOM: true, fatal: !loose }).decode(input) // TODO: cache decoder?
72
+ }
73
+
74
+ const decode = isDeno ? decodeDecoder : decodeNode
75
+
76
+ export const utf16fromString = (str, format = 'uint16') => encode(str, false, format)
77
+ export const utf16fromStringLoose = (str, format = 'uint16') => encode(str, true, format)
78
+ export const utf16toString = (arr, format = 'uint16') => decode(arr, false, format)
79
+ export const utf16toStringLoose = (arr, format = 'uint16') => decode(arr, true, format)
package/utf8.js ADDED
@@ -0,0 +1,80 @@
1
+ import { assertUint8 } from './assert.js'
2
+ import { typedView } from './array.js'
3
+ import { isHermes, nativeDecoder, nativeEncoder } from './fallback/_utils.js'
4
+ import { asciiPrefix, decodeLatin1 } from './fallback/latin1.js'
5
+ import * as js from './fallback/utf8.js'
6
+
7
+ const { TextDecoder, decodeURIComponent, escape } = globalThis // Buffer is optional
8
+ // ignoreBOM: true means that BOM will be left as-is, i.e. will be present in the output
9
+ // We don't want to strip anything unexpectedly
10
+ const decoderLoose = nativeDecoder
11
+ const decoderFatal = nativeDecoder
12
+ ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true })
13
+ : null
14
+ const { isWellFormed } = String.prototype
15
+
16
+ const { E_STRICT, E_STRICT_UNICODE } = js
17
+
18
+ const shouldUseEscapePath = isHermes // faster only on Hermes, js path beats it on normal engines
19
+
20
+ function deLoose(str, loose, res) {
21
+ if (loose || str.length === res.length) return res // length is equal only for ascii, which is automatically fine
22
+ if (isWellFormed) {
23
+ // We have a fast native method
24
+ if (isWellFormed.call(str)) return res
25
+ throw new TypeError(E_STRICT_UNICODE)
26
+ }
27
+
28
+ // Recheck if the string was encoded correctly
29
+ let start = 0
30
+ const last = res.length - 2
31
+ // Search for EFBFBD
32
+ while (start < last) {
33
+ const pos = res.indexOf(0xef, start)
34
+ if (pos === -1) break
35
+ start = pos + 1
36
+ if (res[pos + 1] === 0xbf && res[pos + 2] === 0xbd) {
37
+ // Found a replacement char in output, need to recheck if we encoded the input correctly
38
+ if (str !== decode(res)) throw new TypeError(E_STRICT_UNICODE)
39
+ return res
40
+ }
41
+ }
42
+
43
+ return res
44
+ }
45
+
46
+ function encode(str, loose = false) {
47
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
48
+ if (str.length === 0) return new Uint8Array() // faster than Uint8Array.of
49
+ if (nativeEncoder) return deLoose(str, loose, nativeEncoder.encode(str))
50
+ // No reason to use unescape + encodeURIComponent: it's slower than JS on normal engines, and modern Hermes already has TextEncoder
51
+ return js.encode(str, loose)
52
+ }
53
+
54
+ function decode(arr, loose = false) {
55
+ assertUint8(arr)
56
+ if (arr.byteLength === 0) return ''
57
+ if (nativeDecoder) return loose ? decoderLoose.decode(arr) : decoderFatal.decode(arr) // Node.js and browsers
58
+
59
+ // Fast path for ASCII prefix, this is faster than all alternatives below
60
+ const prefix = decodeLatin1(arr, 0, asciiPrefix(arr))
61
+ if (prefix.length === arr.length) return prefix
62
+
63
+ // This codepath gives a ~3x perf boost on Hermes
64
+ if (shouldUseEscapePath && escape && decodeURIComponent) {
65
+ const o = escape(decodeLatin1(arr, prefix.length, arr.length))
66
+ try {
67
+ return prefix + decodeURIComponent(o) // Latin1 to utf8
68
+ } catch {
69
+ if (!loose) throw new TypeError(E_STRICT)
70
+ // Ok, we have to use manual implementation for loose decoder
71
+ }
72
+ }
73
+
74
+ return prefix + js.decode(arr, loose, prefix.length)
75
+ }
76
+
77
+ export const utf8fromString = (str, format = 'uint8') => typedView(encode(str, false), format)
78
+ export const utf8fromStringLoose = (str, format = 'uint8') => typedView(encode(str, true), format)
79
+ export const utf8toString = (arr) => decode(arr, false)
80
+ export const utf8toStringLoose = (arr) => decode(arr, true)
package/utf8.node.js ADDED
@@ -0,0 +1,54 @@
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('utf-8', { ignoreBOM: true, fatal: true })
9
+ const decoderLoose = new TextDecoder('utf-8', { ignoreBOM: true })
10
+ const { isWellFormed } = String.prototype
11
+ const isDeno = Boolean(globalThis.Deno)
12
+
13
+ function encode(str, loose = false) {
14
+ if (typeof str !== 'string') throw new TypeError('Input is not a string')
15
+ const strLength = str.length
16
+ if (strLength === 0) return new Uint8Array() // faster than Uint8Array.of
17
+ let res
18
+ if (strLength > 0x4_00 && !isDeno) {
19
+ // Faster for large strings
20
+ const byteLength = Buffer.byteLength(str)
21
+ res = Buffer.allocUnsafe(byteLength)
22
+ const ascii = byteLength === strLength
23
+ const written = ascii ? res.latin1Write(str) : res.utf8Write(str)
24
+ if (written !== byteLength) throw new Error('Failed to write all bytes') // safeguard just in case
25
+ if (ascii || loose) return res // no further checks needed
26
+ } else {
27
+ res = Buffer.from(str)
28
+ if (res.length === strLength || loose) return res
29
+ }
30
+
31
+ if (!isWellFormed.call(str)) throw new TypeError(E_STRICT_UNICODE)
32
+ return res
33
+ }
34
+
35
+ function decode(arr, loose = false) {
36
+ assertUint8(arr)
37
+ const byteLength = arr.byteLength
38
+ if (byteLength === 0) return ''
39
+ if (byteLength > 0x6_00 && !(isDeno && loose) && isAscii(arr)) {
40
+ // On non-ascii strings, this loses ~10% * [relative position of the first non-ascii byte] (up to 10% total)
41
+ // On ascii strings, this wins 1.5x on loose = false and 1.3x on loose = true
42
+ // Only makes sense for large enough strings
43
+ const buf = Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength)
44
+ if (isDeno) return buf.toString() // Deno suffers from .latin1Slice
45
+ return buf.latin1Slice(0, arr.byteLength) // .latin1Slice is faster than .asciiSlice
46
+ }
47
+
48
+ return loose ? decoderLoose.decode(arr) : decoderFatal.decode(arr)
49
+ }
50
+
51
+ export const utf8fromString = (str, format = 'uint8') => typedView(encode(str, false), format)
52
+ export const utf8fromStringLoose = (str, format = 'uint8') => typedView(encode(str, true), format)
53
+ export const utf8toString = (arr) => decode(arr, false)
54
+ export const utf8toStringLoose = (arr) => decode(arr, true)
package/wif.js ADDED
@@ -0,0 +1,42 @@
1
+ import { toBase58checkSync, fromBase58checkSync } from './base58check.js'
2
+ import { assertUint8 } from './assert.js'
3
+
4
+ // Mostly matches npmjs.com/wif, but with extra checks + using our base58check
5
+ // Also no inconsistent behavior on Buffer/Uint8Array input
6
+
7
+ function from(arr, expectedVersion) {
8
+ assertUint8(arr)
9
+ const version = arr[0]
10
+ if (expectedVersion !== undefined && version !== expectedVersion) {
11
+ throw new Error('Invalid network version')
12
+ }
13
+
14
+ // Makes a copy, regardless of input being a Buffer or a Uint8Array (unlike .slice)
15
+ const privateKey = Uint8Array.from(arr.subarray(1, 33))
16
+ if (arr.length === 33) return { version, privateKey, compressed: false }
17
+ if (arr.length !== 34) throw new Error('Invalid WIF length')
18
+ if (arr[33] !== 1) throw new Error('Invalid compression flag')
19
+ return { version, privateKey, compressed: true }
20
+ }
21
+
22
+ function to({ version: v, privateKey, compressed }) {
23
+ if (!Number.isSafeInteger(v) || v < 0 || v > 0xff) throw new Error('Missing or invalid version')
24
+ assertUint8(privateKey, { length: 32, name: 'privateKey' })
25
+ if (privateKey.length !== 32) throw new TypeError('Invalid privateKey length')
26
+ const out = new Uint8Array(compressed ? 34 : 33)
27
+ out[0] = v
28
+ out.set(privateKey, 1)
29
+ if (compressed) out[33] = 1
30
+ return out
31
+ }
32
+
33
+ // Async performance is worse here, so expose the same internal methods as sync for now
34
+ // ./base58check is sync internally anyway for now, so doesn't matter until that is changed
35
+
36
+ export const fromWifStringSync = (string, version) => from(fromBase58checkSync(string), version)
37
+ // export const fromWifString = async (string, version) => from(await fromBase58check(string), version)
38
+ export const fromWifString = async (string, version) => from(fromBase58checkSync(string), version)
39
+
40
+ export const toWifStringSync = (wif) => toBase58checkSync(to(wif))
41
+ // export const toWifString = async (wif) => toBase58check(to(wif))
42
+ export const toWifString = async (wif) => toBase58checkSync(to(wif))