@exodus/bytes 1.0.0-rc.6 → 1.0.0-rc.8

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/README.md CHANGED
@@ -1,11 +1,27 @@
1
1
  # `@exodus/bytes`
2
2
 
3
- `Uint8Array` conversion to and from `base64`, `base32`, `hex` and `utf8`
3
+ `Uint8Array` conversion to and from `base64`, `base32`, `base58`, `hex`, and `utf8`
4
4
 
5
5
  [Fast](./Performance.md)
6
6
 
7
7
  Performs proper input validation
8
8
 
9
+ ## API
10
+
11
+ ### `@exodus/bytes/hex.js`
12
+
13
+ ### `@exodus/bytes/base64.js`
14
+
15
+ ### `@exodus/bytes/base32.js`
16
+
17
+ ### `@exodus/bytes/hex.js`
18
+
19
+ ### `@exodus/bytes/base58.js`
20
+
21
+ ### `@exodus/bytes/base58check.js`
22
+
23
+ ### `@exodus/bytes/wif.js`
24
+
9
25
  ## License
10
26
 
11
27
  [MIT](./LICENSE)
package/base58.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { typedView } from './array.js'
2
2
  import { assertUint8 } from './assert.js'
3
- import { nativeDecoder, nativeEncoder } from './fallback/_utils.js'
4
- import { encodeAscii } from './fallback/latin1.js'
3
+ import { nativeDecoder, nativeEncoder, isHermes } from './fallback/_utils.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)))
@@ -20,7 +20,7 @@ let fromMap
20
20
 
21
21
  const E_CHAR = 'Invalid character in base58 input'
22
22
 
23
- const shouldUseBigIntFrom = Boolean(globalThis.HermesInternal) // faster only on Hermes, numbers path beats it on normal engines
23
+ const shouldUseBigIntFrom = isHermes // faster only on Hermes, numbers path beats it on normal engines
24
24
 
25
25
  export function toBase58(arr) {
26
26
  assertUint8(arr)
@@ -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[i++] = c - carry * 58
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) + nativeDecoder.decode(oa)
115
+ return ZERO.repeat(zeros) + decodeAscii(oa)
116
116
  }
117
117
 
118
118
  let out = ''
@@ -134,7 +134,7 @@ export function fromBase58(str, format = 'uint8') {
134
134
  for (let i = 0; i < 58; i++) fromMap[alphabet[i].charCodeAt(0)] = i
135
135
  }
136
136
 
137
- const size = zeros + (((length - zeros) * 3 + 1) >> 2) // 3/4 rounded up, larger than ~0.73 coef to fit everything
137
+ const size = zeros + (((length - zeros + 1) * 3) >> 2) // 3/4 rounded up, larger than ~0.73 coef to fit everything
138
138
  const res = new Uint8Array(size)
139
139
  let at = size // where is the first significant byte written
140
140
 
package/base58check.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { typedView } from './array.js'
2
2
  import { assertUint8 } from './assert.js'
3
3
  import { toBase58, fromBase58 } from './base58.js'
4
- import { hashSync } from '@exodus/crypto/hash'
4
+ import { hashSync } from '@exodus/crypto/hash' // eslint-disable-line @exodus/import/no-deprecated
5
5
 
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
@@ -56,6 +56,7 @@ export const makeBase58check = (hashAlgo, hashAlgoSync) => {
56
56
  }
57
57
  }
58
58
 
59
+ // eslint-disable-next-line @exodus/import/no-deprecated
59
60
  const hash256sync = (x) => hashSync('sha256', hashSync('sha256', x, 'uint8'), 'uint8')
60
61
  const hash256 = hash256sync // See note at the top
61
62
  const {
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 { isHermes, skipWeb } from './fallback/_utils.js'
3
4
  import { decodeLatin1, encodeLatin1 } from './fallback/latin1.js'
4
5
  import * as js from './fallback/base64.js'
5
6
 
@@ -15,8 +16,8 @@ const { toBase64: web64 } = Uint8Array.prototype // Modern engines have this
15
16
  const { E_CHAR, E_PADDING, E_LENGTH, E_LAST } = js
16
17
 
17
18
  // 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)
19
+ const shouldUseBtoa = btoa && isHermes
20
+ const shouldUseAtob = atob && isHermes
20
21
 
21
22
  // For native Buffer codepaths only
22
23
  const isBuffer = (x) => x.constructor === Buffer && Buffer.isBuffer(x)
@@ -33,13 +34,12 @@ function maybePad(res, padding) {
33
34
  }
34
35
 
35
36
  const toUrl = (x) => x.replaceAll('+', '-').replaceAll('/', '_')
36
- const fromUrl = (x) => x.replaceAll('-', '+').replaceAll('_', '/')
37
- const haveWeb = (x) => web64 && x.toBase64 === web64
37
+ const haveWeb = (x) => !skipWeb && web64 && x.toBase64 === web64
38
38
 
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).toString('base64'), padding) // Older Node.js
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).toString('base64url'), padding) // Older Node.js
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
  }
@@ -111,7 +111,7 @@ function noWhitespaceSeen(str, arr) {
111
111
  }
112
112
 
113
113
  let fromBase64impl
114
- if (Uint8Array.fromBase64) {
114
+ if (!skipWeb && Uint8Array.fromBase64) {
115
115
  // NOTICE: this is actually slower than our JS impl in older JavaScriptCore and (slightly) in SpiderMonkey, but faster on V8 and new JavaScriptCore
116
116
  fromBase64impl = (str, isBase64url, padding) => {
117
117
  const alphabet = isBase64url ? 'base64url' : 'base64'
@@ -137,40 +137,41 @@ if (Uint8Array.fromBase64) {
137
137
  if (!noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR)
138
138
  return arr
139
139
  }
140
- } else {
140
+ } else if (haveNativeBuffer) {
141
+ fromBase64impl = (str, isBase64url, padding) => {
142
+ const arr = Buffer.from(str, 'base64')
143
+ // Rechecking by re-encoding is cheaper than regexes on Node.js
144
+ const got = isBase64url ? maybeUnpad(str, padding === false) : maybePad(str, padding !== true)
145
+ const valid = isBase64url ? arr.base64urlSlice(0, arr.length) : arr.base64Slice(0, arr.length)
146
+ if (got !== valid) throw new SyntaxError(E_PADDING)
147
+ return arr // fully checked
148
+ }
149
+ } else if (shouldUseAtob) {
150
+ // atob is faster than manual parsing on Hermes
141
151
  fromBase64impl = (str, isBase64url, padding) => {
142
152
  let arr
143
- if (haveNativeBuffer) {
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)
148
- } else if (shouldUseAtob) {
149
- // atob is faster than manual parsing on Hermes
150
- if (isBase64url) {
151
- if (/[\t\n\f\r +/]/.test(str)) throw new SyntaxError(E_CHAR) // atob verifies other invalid input
152
- str = fromUrl(str)
153
- }
154
-
155
- try {
156
- arr = encodeLatin1(atob(str))
157
- } catch {
158
- throw new SyntaxError(E_CHAR) // convert atob errors
159
- }
153
+ if (isBase64url) {
154
+ if (/[\t\n\f\r +/]/.test(str)) throw new SyntaxError(E_CHAR) // atob verifies other invalid input
155
+ str = str.replaceAll('-', '+').replaceAll('_', '/') // from url to normal
156
+ }
160
157
 
161
- if (!isBase64url && !noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR) // base64url checks input above
162
- } else {
163
- return js.fromBase64(str, isBase64url) // early return to skip last chunk verification, it's already validated in js
158
+ try {
159
+ arr = encodeLatin1(atob(str))
160
+ } catch {
161
+ throw new SyntaxError(E_CHAR) // convert atob errors
164
162
  }
165
163
 
164
+ if (!isBase64url && !noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR) // base64url checks input above
165
+
166
166
  if (arr.length % 3 !== 0) {
167
167
  // Check last chunk to be strict if it was incomplete
168
- const expected = toBase64(arr.subarray(-(arr.length % 3)))
168
+ const expected = toBase64(arr.subarray(-(arr.length % 3))) // str is normalized to non-url already
169
169
  const end = str.length % 4 === 0 ? str.slice(-4) : str.slice(-(str.length % 4)).padEnd(4, '=')
170
- const actual = isBase64url ? fromUrl(end) : end
171
- if (expected !== actual) throw new SyntaxError(E_LAST)
170
+ if (expected !== end) throw new SyntaxError(E_LAST)
172
171
  }
173
172
 
174
173
  return arr
175
174
  }
175
+ } else {
176
+ fromBase64impl = (str, isBase64url, padding) => js.fromBase64(str, isBase64url) // validated in js
176
177
  }
package/bech32.js ADDED
@@ -0,0 +1,254 @@
1
+ import { assertUint8 } from './assert.js'
2
+ import { nativeEncoder } from './fallback/_utils.js'
3
+ import { decodeAscii, encodeAscii, encodeLatin1 } from './fallback/latin1.js'
4
+
5
+ const alphabet = [...'qpzry9x8gf2tvdw0s3jn54khce6mua7l']
6
+ const BECH32 = 1
7
+ const BECH32M = 0x2b_c8_30_a3
8
+
9
+ const E_SIZE = 'Input length is out of range'
10
+ const E_PREFIX = 'Missing or invalid prefix'
11
+ const E_MIXED = 'Mixed-case string'
12
+ const E_PADDING = 'Padding is invalid'
13
+ const E_CHECKSUM = 'Invalid checksum'
14
+ const E_CHARACTER = 'Non-bech32 character'
15
+ const E_STRING = 'Input is not a string'
16
+
17
+ // nativeEncoder path uses encodeAscii which asserts ascii, otherwise we have 0-255 bytes from encodeLatin1
18
+ const c2x = new Int8Array(nativeEncoder ? 128 : 256).fill(-1)
19
+ const x2c = new Uint8Array(32)
20
+ for (let i = 0; i < alphabet.length; i++) {
21
+ const c = alphabet[i].charCodeAt(0)
22
+ c2x[c] = i
23
+ x2c[i] = c
24
+ }
25
+
26
+ // checksum size is 30 bits, 0x3f_ff_ff_ff
27
+ // The good thing about the checksum is that it's linear over every bit
28
+ const poly0 = new Uint32Array(32) // just precache all possible ones, it's only 1 KiB
29
+ const p = (x) => ((x & 0x1_ff_ff_ff) << 5) ^ poly0[x >> 25]
30
+ for (let i = 0; i < 32; i++) {
31
+ poly0[i] =
32
+ (i & 0b0_0001 ? 0x3b_6a_57_b2 : 0) ^
33
+ (i & 0b0_0010 ? 0x26_50_8e_6d : 0) ^
34
+ (i & 0b0_0100 ? 0x1e_a1_19_fa : 0) ^
35
+ (i & 0b0_1000 ? 0x3d_42_33_dd : 0) ^
36
+ (i & 0b1_0000 ? 0x2a_14_62_b3 : 0)
37
+ }
38
+
39
+ // 7 KiB more for faster p6/p8
40
+ const poly1 = new Uint32Array(32)
41
+ const poly2 = new Uint32Array(32)
42
+ const poly3 = new Uint32Array(32)
43
+ const poly4 = new Uint32Array(32)
44
+ const poly5 = new Uint32Array(32)
45
+ const poly6 = new Uint32Array(32)
46
+ const poly7 = new Uint32Array(32)
47
+ for (let i = 0; i < 32; i++) {
48
+ // poly0[i] === p(p(p(p(p(p(i))))))
49
+ poly1[i] = p(poly0[i]) // aka p(p(p(p(p(p(i << 5))))))
50
+ poly2[i] = p(poly1[i]) // aka p(p(p(p(p(p(i << 10))))))
51
+ poly3[i] = p(poly2[i]) // aka p(p(p(p(p(p(i << 15))))))
52
+ poly4[i] = p(poly3[i]) // aka p(p(p(p(p(p(i << 20))))))
53
+ poly5[i] = p(poly4[i]) // aka p(p(p(p(p(p(i << 25))))))
54
+ poly6[i] = p(poly5[i])
55
+ poly7[i] = p(poly6[i])
56
+ }
57
+
58
+ function p6(x) {
59
+ // Same as: return p(p(p(p(p(p(x))))))
60
+ const x0 = x & 0x1f
61
+ const x1 = (x >> 5) & 0x1f
62
+ const x2 = (x >> 10) & 0x1f
63
+ const x3 = (x >> 15) & 0x1f
64
+ const x4 = (x >> 20) & 0x1f
65
+ const x5 = (x >> 25) & 0x1f
66
+ return poly0[x0] ^ poly1[x1] ^ poly2[x2] ^ poly3[x3] ^ poly4[x4] ^ poly5[x5]
67
+ }
68
+
69
+ function p8(x) {
70
+ // Same as: return p(p(p(p(p(p(p(p(x))))))))
71
+ const x0 = x & 0x1f
72
+ const x1 = (x >> 5) & 0x1f
73
+ const x2 = (x >> 10) & 0x1f
74
+ const x3 = (x >> 15) & 0x1f
75
+ const x4 = (x >> 20) & 0x1f
76
+ const x5 = (x >> 25) & 0x1f
77
+ return poly2[x0] ^ poly3[x1] ^ poly4[x2] ^ poly5[x3] ^ poly6[x4] ^ poly7[x5]
78
+ }
79
+
80
+ // p(p(p(p(p(p(chk) ^ x0) ^ x1) ^ x2) ^ x3) ^ x4) ^ x5 === p6(chk) ^ merge(x0, x1, x2, x3, x4, x5)
81
+ const merge = (a, b, c, d, e, f) => f ^ (e << 5) ^ (d << 10) ^ (c << 15) ^ (b << 20) ^ (a << 25)
82
+
83
+ const prefixCache = new Map() // Cache 10 of them
84
+
85
+ function pPrefix(prefix) {
86
+ if (prefix === 'bc') return 0x2_31_80_43 // perf
87
+ const cached = prefixCache.get(prefix)
88
+ if (cached !== undefined) return cached
89
+
90
+ // bech32_hrp_expand(s): [ord(x) >> 5 for x in s] + [0] + [ord(x) & 31 for x in s]
91
+ // We can do this in a single scan due to linearity, but it's not very beneficial
92
+ let chk = 1 // it starts with one (see def bech32_polymod in BIP_0173)
93
+ const length = prefix.length
94
+ for (let i = 0; i < length; i++) {
95
+ const c = prefix.charCodeAt(i)
96
+ if (c < 33 || c > 126) throw new Error(E_PREFIX) // each character having a value in the range [33-126]
97
+ chk = p(chk) ^ (c >> 5)
98
+ }
99
+
100
+ chk = p(chk) // <= for + [0]
101
+ for (let i = 0; i < length; i++) {
102
+ const c = prefix.charCodeAt(i)
103
+ chk = p(chk) ^ (c & 0x1f)
104
+ }
105
+
106
+ if (prefixCache.size < 10) prefixCache.set(prefix, chk)
107
+ return chk
108
+ }
109
+
110
+ function toBech32enc(prefix, bytes, limit, encoding) {
111
+ if (typeof prefix !== 'string' || !prefix) throw new TypeError(E_PREFIX)
112
+ if (typeof limit !== 'number') throw new TypeError(E_SIZE)
113
+ assertUint8(bytes)
114
+ const bytesLength = bytes.length
115
+ const wordsLength = Math.ceil((bytesLength * 8) / 5)
116
+ if (!(prefix.length + 7 + wordsLength <= limit)) throw new TypeError(E_SIZE)
117
+ prefix = prefix.toLowerCase()
118
+ const out = new Uint8Array(wordsLength + 6)
119
+
120
+ let chk = pPrefix(prefix)
121
+ let i = 0, j = 0 // prettier-ignore
122
+
123
+ // This loop is just an optimization of the next one
124
+ for (const length4 = bytesLength - 4; i < length4; i += 5, j += 8) {
125
+ const b0 = bytes[i], b1 = bytes[i + 1], b2 = bytes[i + 2], b3 = bytes[i + 3], b4 = bytes[i + 4] // prettier-ignore
126
+ const x0 = b0 >> 3
127
+ const x1 = ((b0 << 2) & 0x1f) | (b1 >> 6)
128
+ const x2 = (b1 >> 1) & 0x1f
129
+ const x3 = ((b1 << 4) & 0x1f) | (b2 >> 4)
130
+ const x4 = ((b2 << 1) & 0x1f) | (b3 >> 7)
131
+ const x5 = (b3 >> 2) & 0x1f
132
+ const x6 = ((b3 << 3) & 0x1f) | (b4 >> 5)
133
+ const x7 = b4 & 0x1f
134
+ chk = merge(x2, x3, x4, x5, x6, x7) ^ poly0[x1] ^ poly1[x0] ^ p8(chk)
135
+ out[j] = x2c[x0]
136
+ out[j + 1] = x2c[x1]
137
+ out[j + 2] = x2c[x2]
138
+ out[j + 3] = x2c[x3]
139
+ out[j + 4] = x2c[x4]
140
+ out[j + 5] = x2c[x5]
141
+ out[j + 6] = x2c[x6]
142
+ out[j + 7] = x2c[x7]
143
+ }
144
+
145
+ let value = 0, bits = 0 // prettier-ignore
146
+ for (; i < bytesLength; i++) {
147
+ value = ((value & 0xf) << 8) | bytes[i]
148
+ bits += 3
149
+ const x = (value >> bits) & 0x1f
150
+ chk = p(chk) ^ x
151
+ out[j++] = x2c[x]
152
+ if (bits >= 5) {
153
+ bits -= 5
154
+ const x = (value >> bits) & 0x1f
155
+ chk = p(chk) ^ x
156
+ out[j++] = x2c[x]
157
+ }
158
+ }
159
+
160
+ if (bits > 0) {
161
+ const x = (value << (5 - bits)) & 0x1f
162
+ chk = p(chk) ^ x
163
+ out[j++] = x2c[x]
164
+ }
165
+
166
+ chk = encoding ^ p6(chk)
167
+ out[j++] = x2c[(chk >> 25) & 0x1f]
168
+ out[j++] = x2c[(chk >> 20) & 0x1f]
169
+ out[j++] = x2c[(chk >> 15) & 0x1f]
170
+ out[j++] = x2c[(chk >> 10) & 0x1f]
171
+ out[j++] = x2c[(chk >> 5) & 0x1f]
172
+ out[j++] = x2c[(chk >> 0) & 0x1f]
173
+
174
+ return prefix + '1' + decodeAscii(out) // suboptimal in barebones, but actually ok in Hermes for not to care atm
175
+ }
176
+
177
+ function assertDecodeArgs(str, limit) {
178
+ if (typeof str !== 'string') throw new TypeError(E_STRING)
179
+ if (typeof limit !== 'number' || str.length < 8 || !(str.length <= limit)) throw new Error(E_SIZE)
180
+ }
181
+
182
+ function fromBech32enc(str, limit, encoding) {
183
+ assertDecodeArgs(str, limit)
184
+ const lower = str.toLowerCase()
185
+ if (str !== lower) {
186
+ if (str !== str.toUpperCase()) throw new Error(E_MIXED)
187
+ str = lower
188
+ }
189
+
190
+ const split = str.lastIndexOf('1')
191
+ if (split <= 0) throw new Error(E_PREFIX)
192
+ const prefix = str.slice(0, split)
193
+ const charsLength = str.length - split - 1
194
+ const wordsLength = charsLength - 6
195
+ if (wordsLength < 0) throw new Error(E_SIZE)
196
+ const bytesLength = (wordsLength * 5) >> 3
197
+ const slice = str.slice(split + 1)
198
+ const c = nativeEncoder ? encodeAscii(slice, E_CHARACTER) : encodeLatin1(slice) // suboptimal, but only affects non-Hermes barebones
199
+ const bytes = new Uint8Array(bytesLength)
200
+
201
+ let chk = pPrefix(prefix)
202
+ let i = 0, j = 0 // prettier-ignore
203
+
204
+ // This loop is just an optimization of the next one
205
+ for (const length7 = wordsLength - 7; i < length7; i += 8, j += 5) {
206
+ const c0 = c[i], c1 = c[i + 1], c2 = c[i + 2], c3 = c[i + 3], c4 = c[i + 4], c5 = c[i + 5], c6 = c[i + 6], c7 = c[i + 7] // prettier-ignore
207
+ const x0 = c2x[c0], x1 = c2x[c1], x2 = c2x[c2], x3 = c2x[c3], x4 = c2x[c4], x5 = c2x[c5], x6 = c2x[c6], x7 = c2x[c7] // prettier-ignore
208
+ if (x0 < 0 || x1 < 0 || x2 < 0 || x3 < 0 || x4 < 0 || x5 < 0 || x6 < 0 || x7 < 0) throw new SyntaxError(E_CHARACTER) // prettier-ignore
209
+ chk = merge(x2, x3, x4, x5, x6, x7) ^ poly0[x1] ^ poly1[x0] ^ p8(chk)
210
+ bytes[j] = (x0 << 3) | (x1 >> 2)
211
+ bytes[j + 1] = (((x1 << 6) | (x2 << 1)) & 0xff) | (x3 >> 4)
212
+ bytes[j + 2] = ((x3 << 4) & 0xff) | (x4 >> 1)
213
+ bytes[j + 3] = ((((x4 << 5) | x5) << 2) & 0xff) | (x6 >> 3)
214
+ bytes[j + 4] = ((x6 << 5) & 0xff) | x7
215
+ }
216
+
217
+ let value = 0, bits = 0 // prettier-ignore
218
+ for (; i < wordsLength; i++) {
219
+ const x = c2x[c[i]]
220
+ if (x < 0) throw new SyntaxError(E_CHARACTER)
221
+ chk = p(chk) ^ x
222
+ value = (value << 5) | x
223
+ bits += 5
224
+ if (bits >= 8) {
225
+ bits -= 8
226
+ bytes[j++] = (value >> bits) & 0xff
227
+ }
228
+ }
229
+
230
+ if (bits >= 5 || (value << (8 - bits)) & 0xff) throw new Error(E_PADDING)
231
+
232
+ // Checksum
233
+ {
234
+ const c0 = c[i], c1 = c[i + 1], c2 = c[i + 2], c3 = c[i + 3], c4 = c[i + 4], c5 = c[i + 5] // prettier-ignore
235
+ const x0 = c2x[c0], x1 = c2x[c1], x2 = c2x[c2], x3 = c2x[c3], x4 = c2x[c4], x5 = c2x[c5] // prettier-ignore
236
+ if (x0 < 0 || x1 < 0 || x2 < 0 || x3 < 0 || x4 < 0 || x5 < 0) throw new SyntaxError(E_CHARACTER)
237
+ if ((merge(x0, x1, x2, x3, x4, x5) ^ p6(chk)) !== encoding) throw new Error(E_CHECKSUM)
238
+ }
239
+
240
+ return { prefix, bytes }
241
+ }
242
+
243
+ // This is designed to be a very quick check, skipping all other validation
244
+ export function getPrefix(str, limit = 90) {
245
+ assertDecodeArgs(str, limit)
246
+ const split = str.lastIndexOf('1')
247
+ if (split <= 0) throw new Error(E_PREFIX)
248
+ return str.slice(0, split).toLowerCase()
249
+ }
250
+
251
+ export const toBech32 = (prefix, bytes, limit = 90) => toBech32enc(prefix, bytes, limit, BECH32)
252
+ export const fromBech32 = (str, limit = 90) => fromBech32enc(str, limit, BECH32)
253
+ export const toBech32m = (prefix, bytes, limit = 90) => toBech32enc(prefix, bytes, limit, BECH32M)
254
+ export const fromBech32m = (str, limit = 90) => fromBech32enc(str, limit, BECH32M)
@@ -3,4 +3,38 @@ 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
- export { nativeEncoder, nativeDecoder }
6
+ const nativeBuffer = haveNativeBuffer ? Buffer : null
7
+ const isHermes = Boolean(globalThis.HermesInternal)
8
+
9
+ // Actually windows-1252, compatible with ascii and latin1 decoding
10
+ // Beware that on non-latin1, i.e. on windows-1252, this is broken in ~all Node.js versions released
11
+ // in 2025 due to a regression, so we call it Latin1 as it's usable only for that
12
+ const nativeDecoderLatin1 = isNative(TextDecoder)
13
+ ? new TextDecoder('latin1', { ignoreBOM: true })
14
+ : null
15
+
16
+ // Block Firefox < 146 specifically from using native hex/base64, as it's very slow there
17
+ // Refs: https://bugzilla.mozilla.org/show_bug.cgi?id=1994067 (and linked issues), fixed in 146
18
+ // Before that, all versions of Firefox >= 133 are slow
19
+ // TODO: this could be removed when < 146 usage diminishes (note ESR)
20
+ // We do not worry about false-negatives here but worry about false-positives!
21
+ function shouldSkipBuiltins() {
22
+ const g = globalThis
23
+ // First, attempt to exclude as many things as we can using trivial checks, just in case, and to not hit ua
24
+ if (haveNativeBuffer || isHermes || !g.window || g.chrome || !g.navigator) return false
25
+ try {
26
+ // This was fixed specifically in Firefox 146. Other engines except Hermes (already returned) get this right
27
+ new WeakSet().add(Symbol()) // eslint-disable-line symbol-description
28
+ return false
29
+ } catch {
30
+ // In catch and not after in case if something too smart optimizes out code in try. False-negative is acceptable in that case
31
+ if (!('onmozfullscreenerror' in g)) return false // Firefox has it (might remove in the future, but we don't care)
32
+ return /firefox/i.test(g.navigator.userAgent || '') // as simple as we can
33
+ }
34
+
35
+ return false // eslint-disable-line no-unreachable
36
+ }
37
+
38
+ const skipWeb = shouldSkipBuiltins()
39
+
40
+ export { nativeEncoder, nativeDecoder, nativeDecoderLatin1, nativeBuffer, isHermes, skipWeb }
@@ -1,6 +1,6 @@
1
1
  import { assertUint8 } from '../assert.js'
2
- import { nativeEncoder, nativeDecoder } from './_utils.js'
3
- import { encodeAscii } from './latin1.js'
2
+ import { nativeEncoder, nativeDecoder, isHermes } from './_utils.js'
3
+ import { encodeAscii, decodeAscii } from './latin1.js'
4
4
 
5
5
  // See https://datatracker.ietf.org/doc/html/rfc4648
6
6
 
@@ -14,7 +14,7 @@ export const E_PADDING = 'Invalid base32 padding'
14
14
  export const E_LENGTH = 'Invalid base32 length'
15
15
  export const E_LAST = 'Invalid last chunk'
16
16
 
17
- const useTemplates = Boolean(globalThis.HermesInternal) // Faster on Hermes and JSC, but we use it only on Hermes
17
+ const useTemplates = isHermes // Faster on Hermes and JSC, but we use it only on Hermes
18
18
 
19
19
  // We construct output by concatenating chars, this seems to be fine enough on modern JS engines
20
20
  export function toBase32(arr, isBase32Hex, padding) {
@@ -68,7 +68,7 @@ export function toBase32(arr, isBase32Hex, padding) {
68
68
  j += 4
69
69
  }
70
70
 
71
- o = nativeDecoder.decode(oa)
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) {
@@ -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 = nativeDecoder.decode(oa)
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
- import { nativeDecoder, nativeEncoder } from './_utils.js'
3
- import { encodeAscii } from './latin1.js'
2
+ import { nativeDecoder, nativeEncoder, isHermes } from './_utils.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
@@ -61,7 +61,7 @@ function toHexPartTemplates(a, start, end) {
61
61
 
62
62
  // Using templates is significantly faster in Hermes and JSC
63
63
  // It's harder to detect JSC and not important anyway as it has native impl, so we detect only Hermes
64
- const toHexPart = globalThis.HermesInternal ? toHexPartTemplates : toHexPartAddition
64
+ const toHexPart = isHermes ? toHexPartTemplates : toHexPartAddition
65
65
 
66
66
  export function toHex(arr) {
67
67
  assertUint8(arr)
@@ -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 nativeDecoder.decode(oa)
100
+ return decodeAscii(oa)
101
101
  }
102
102
 
103
103
  if (length > 30_000) {
@@ -1,4 +1,10 @@
1
- import { nativeEncoder } from './_utils.js'
1
+ import {
2
+ nativeEncoder,
3
+ nativeDecoder,
4
+ nativeDecoderLatin1,
5
+ nativeBuffer,
6
+ isHermes,
7
+ } from './_utils.js'
2
8
 
3
9
  // See http://stackoverflow.com/a/22747272/680742, which says that lowest limit is in Chrome, with 0xffff args
4
10
  // On Hermes, actual max is 0x20_000 minus current stack depth, 1/16 of that should be safe
@@ -32,6 +38,7 @@ export function asciiPrefix(arr) {
32
38
  return length
33
39
  }
34
40
 
41
+ // Capable of decoding Uint16Array to UTF-16 as well as Uint8Array to Latin-1
35
42
  export function decodeLatin1(arr, start = 0, stop = arr.length) {
36
43
  start |= 0
37
44
  stop |= 0
@@ -52,10 +59,23 @@ export function decodeLatin1(arr, start = 0, stop = arr.length) {
52
59
  return String.fromCharCode.apply(String, sliced)
53
60
  }
54
61
 
55
- export const encodeLatin1 = globalThis.HermesInternal
56
- ? (str) => {
62
+ // Does not check input, uses best available method
63
+ // Building an array for this is only faster than proper string concatenation when TextDecoder or native Buffer are available
64
+ export const decodeAscii = nativeBuffer
65
+ ? (a) =>
66
+ // Buffer is faster on Node.js (but only for long enough data), if we know that output is ascii
67
+ a.byteLength >= 0x3_00
68
+ ? nativeBuffer.from(a.buffer, a.byteOffset, a.byteLength).latin1Slice(0, a.byteLength) // .latin1Slice is faster than .asciiSlice
69
+ : nativeDecoder.decode(a) // On Node.js, utf8 decoder is faster than latin1
70
+ : nativeDecoderLatin1
71
+ ? (a) => nativeDecoderLatin1.decode(a) // On browsers (specifically WebKit), latin1 decoder is faster than utf8
72
+ : (a) => decodeLatin1(new Uint8Array(a.buffer, a.byteOffset, a.byteLength)) // Fallback. We shouldn't get here, constructing with strings directly is faster
73
+
74
+ /* eslint-disable @exodus/mutable/no-param-reassign-prop-only */
75
+
76
+ export const encodeCharcodes = isHermes
77
+ ? (str, arr) => {
57
78
  const length = str.length
58
- const arr = new Uint8Array(length)
59
79
  if (length > 64) {
60
80
  const at = str.charCodeAt.bind(str) // faster on strings from ~64 chars on Hermes, but can be 10x slower on e.g. JSC
61
81
  for (let i = 0; i < length; i++) arr[i] = at(i)
@@ -65,16 +85,19 @@ export const encodeLatin1 = globalThis.HermesInternal
65
85
 
66
86
  return arr
67
87
  }
68
- : (str) => {
88
+ : (str, arr) => {
69
89
  const length = str.length
70
- const arr = new Uint8Array(length)
71
90
  // Can be optimized with unrolling, but this is not used on non-Hermes atm
72
91
  for (let i = 0; i < length; i++) arr[i] = str.charCodeAt(i)
73
92
  return arr
74
93
  }
75
94
 
95
+ /* eslint-enable @exodus/mutable/no-param-reassign-prop-only */
96
+
97
+ export const encodeLatin1 = (str) => encodeCharcodes(str, new Uint8Array(str.length))
98
+
76
99
  // Expects nativeEncoder to be present
77
- export const encodeAscii = globalThis.HermesInternal
100
+ export const encodeAscii = isHermes
78
101
  ? (str, ERR) => {
79
102
  // Much faster in Hermes
80
103
  const codes = new Uint8Array(str.length + 4) // overshoot by a full utf8 char
@@ -82,8 +105,15 @@ export const encodeAscii = globalThis.HermesInternal
82
105
  if (info.read !== str.length || info.written !== str.length) throw new SyntaxError(ERR) // non-ascii
83
106
  return codes.subarray(0, str.length)
84
107
  }
85
- : (str, ERR) => {
86
- const codes = nativeEncoder.encode(str)
87
- if (codes.length !== str.length) throw new SyntaxError(ERR) // non-ascii
88
- return codes
89
- }
108
+ : nativeBuffer
109
+ ? (str, ERR) => {
110
+ // TextEncoder is slow on Node.js 24 / 25 (was ok on 22)
111
+ const codes = nativeBuffer.from(str, 'utf8') // ascii/latin1 coerces, we need to check
112
+ if (codes.length !== str.length) throw new SyntaxError(ERR) // non-ascii
113
+ return new Uint8Array(codes.buffer, codes.byteOffset, codes.byteLength)
114
+ }
115
+ : (str, ERR) => {
116
+ const codes = nativeEncoder.encode(str)
117
+ if (codes.length !== str.length) throw new SyntaxError(ERR) // non-ascii
118
+ return codes
119
+ }
package/hex.js CHANGED
@@ -1,31 +1,19 @@
1
1
  import { assertUint8 } from './assert.js'
2
2
  import { typedView } from './array.js'
3
+ import { skipWeb } from './fallback/_utils.js'
3
4
  import * as js from './fallback/hex.js'
4
5
 
5
- const { Buffer } = globalThis // Buffer is optional, only used when native
6
- const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
7
6
  const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
8
7
 
9
- const { E_HEX } = js
10
-
11
8
  export function toHex(arr) {
12
9
  assertUint8(arr)
13
10
  if (arr.length === 0) return ''
14
- if (webHex && arr.toHex === webHex) return arr.toHex()
15
- if (!haveNativeBuffer) return js.toHex(arr)
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
+ if (!skipWeb && webHex && arr.toHex === webHex) return arr.toHex()
12
+ return js.toHex(arr)
18
13
  }
19
14
 
20
15
  // Unlike Buffer.from(), throws on invalid input
21
- export const fromHex = Uint8Array.fromHex
22
- ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
23
- : haveNativeBuffer
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
- }
16
+ export const fromHex =
17
+ !skipWeb && Uint8Array.fromHex
18
+ ? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
31
19
  : (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.6",
3
+ "version": "1.0.0-rc.8",
4
4
  "description": "Various operations on Uint8Array data",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
@@ -50,8 +50,12 @@
50
50
  "/base58.js",
51
51
  "/base58check.js",
52
52
  "/base64.js",
53
+ "/bech32.js",
54
+ "/hex.node.js",
53
55
  "/hex.js",
54
- "/utf8.js"
56
+ "/utf8.node.js",
57
+ "/utf8.js",
58
+ "/wif.js"
55
59
  ],
56
60
  "exports": {
57
61
  "./array.js": "./array.js",
@@ -59,8 +63,20 @@
59
63
  "./base58.js": "./base58.js",
60
64
  "./base58check.js": "./base58check.js",
61
65
  "./base64.js": "./base64.js",
62
- "./hex.js": "./hex.js",
63
- "./utf8.js": "./utf8.js"
66
+ "./bech32.js": "./bech32.js",
67
+ "./hex.js": {
68
+ "node": "./hex.node.js",
69
+ "default": "./hex.js"
70
+ },
71
+ "./utf16.js": {
72
+ "node": "./utf16.node.js",
73
+ "default": "./utf16.js"
74
+ },
75
+ "./utf8.js": {
76
+ "node": "./utf8.node.js",
77
+ "default": "./utf8.js"
78
+ },
79
+ "./wif.js": "./wif.js"
64
80
  },
65
81
  "peerDependencies": {
66
82
  "@exodus/crypto": "^1.0.0-rc.4"
@@ -72,7 +88,7 @@
72
88
  },
73
89
  "devDependencies": {
74
90
  "@ethersproject/strings": "^5.8.0",
75
- "@exodus/crypto": "1.0.0-rc.29",
91
+ "@exodus/crypto": "^1.0.0-rc.30",
76
92
  "@exodus/eslint-config": "^5.24.0",
77
93
  "@exodus/prettier": "^1.0.0",
78
94
  "@exodus/test": "^1.0.0-rc.108",
@@ -84,19 +100,26 @@
84
100
  "base-x": "^5.0.1",
85
101
  "base32.js": "^0.1.0",
86
102
  "base64-js": "^1.5.1",
103
+ "bech32": "^2.0.0",
87
104
  "bs58": "^6.0.0",
88
105
  "bs58check": "^4.0.0",
89
106
  "bstring": "^0.3.9",
90
107
  "buffer": "^6.0.3",
108
+ "decode-utf8": "^1.0.1",
91
109
  "electron": "36.5.0",
110
+ "encode-utf8": "^2.0.0",
92
111
  "eslint": "^8.44.0",
93
112
  "fast-base64-decode": "^2.0.0",
94
113
  "fast-base64-encode": "^1.0.0",
95
114
  "hextreme": "^1.0.7",
96
115
  "hi-base32": "^0.5.1",
116
+ "iconv-lite": "^0.7.0",
97
117
  "jsvu": "^3.0.0",
98
118
  "text-encoding": "^0.7.0",
99
- "typescript": "^5.9.3"
119
+ "typescript": "^5.9.3",
120
+ "uint8array-tools": "^0.0.9",
121
+ "utf8": "^3.0.0",
122
+ "wif": "^5.0.0"
100
123
  },
101
124
  "prettier": "@exodus/prettier",
102
125
  "packageManager": "pnpm@10.12.1+sha256.889bac470ec93ccc3764488a19d6ba8f9c648ad5e50a9a6e4be3768a5de387a3"
package/utf8.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { assertUint8 } from './assert.js'
2
2
  import { typedView } from './array.js'
3
- import * as js from './fallback/utf8.js'
3
+ import { isHermes } from './fallback/_utils.js'
4
4
  import { asciiPrefix, decodeLatin1 } from './fallback/latin1.js'
5
+ import * as js from './fallback/utf8.js'
5
6
 
6
7
  const { Buffer, TextEncoder, TextDecoder, decodeURIComponent, escape } = globalThis // Buffer is optional
7
8
  const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
@@ -16,7 +17,7 @@ const { isWellFormed } = String.prototype
16
17
 
17
18
  const { E_STRICT, E_STRICT_UNICODE } = js
18
19
 
19
- const shouldUseEscapePath = Boolean(globalThis.HermesInternal) // faster only on Hermes, js path beats it on normal engines
20
+ const shouldUseEscapePath = isHermes // faster only on Hermes, js path beats it on normal engines
20
21
 
21
22
  function deLoose(str, loose, res) {
22
23
  if (loose || str.length === res.length) return res // length is equal only for ascii, which is automatically fine
@@ -46,16 +47,16 @@ function deLoose(str, loose, res) {
46
47
 
47
48
  function encode(str, loose = false) {
48
49
  if (typeof str !== 'string') throw new TypeError('Input is not a string')
49
- if (haveNativeBuffer) return deLoose(str, loose, Buffer.from(str)) // faster on ascii on Node.js
50
- if (nativeEncoder) return deLoose(str, loose, nativeEncoder.encode(str)) // Node.js, browsers, and Hermes
50
+ if (str.length === 0) return new Uint8Array() // faster than Uint8Array.of
51
+ if (nativeEncoder) return deLoose(str, loose, nativeEncoder.encode(str))
51
52
  // No reason to use unescape + encodeURIComponent: it's slower than JS on normal engines, and modern Hermes already has TextEncoder
52
53
  return js.encode(str, loose)
53
54
  }
54
55
 
55
56
  function decode(arr, loose = false) {
56
57
  assertUint8(arr)
58
+ if (arr.byteLength === 0) return ''
57
59
  if (haveDecoder) return loose ? decoderLoose.decode(arr) : decoderFatal.decode(arr) // Node.js and browsers
58
- // No reason to use native Buffer: it's not faster than TextDecoder, needs rechecks in non-loose mode, and Node.js has TextDecoder
59
60
 
60
61
  // Fast path for ASCII prefix, this is faster than all alternatives below
61
62
  const prefix = decodeLatin1(arr, 0, asciiPrefix(arr))
package/utf8.node.js ADDED
@@ -0,0 +1,51 @@
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 strLength = str.length
15
+ if (strLength === 0) return new Uint8Array() // faster than Uint8Array.of
16
+ let res
17
+ if (strLength > 0x4_00) {
18
+ // Faster for large strings
19
+ const byteLength = Buffer.byteLength(str)
20
+ res = Buffer.allocUnsafe(byteLength)
21
+ const ascii = byteLength === strLength
22
+ const written = ascii ? res.latin1Write(str) : res.utf8Write(str)
23
+ if (written !== byteLength) throw new Error('Failed to write all bytes') // safeguard just in case
24
+ if (ascii || loose) return res // no further checks needed
25
+ } else {
26
+ res = Buffer.from(str)
27
+ if (res.length === strLength || loose) return res
28
+ }
29
+
30
+ if (!isWellFormed.call(str)) throw new TypeError(E_STRICT_UNICODE)
31
+ return res
32
+ }
33
+
34
+ function decode(arr, loose = false) {
35
+ assertUint8(arr)
36
+ const byteLength = arr.byteLength
37
+ if (byteLength === 0) return ''
38
+ if (byteLength > 0x6_00 && isAscii(arr)) {
39
+ // On non-ascii strings, this loses ~10% * [relative position of the first non-ascii byte] (up to 10% total)
40
+ // On ascii strings, this wins 1.5x on loose = false and 1.3x on loose = true
41
+ // Only makes sense for large enough strings
42
+ return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).latin1Slice(0, arr.byteLength) // .latin1Slice is faster than .asciiSlice
43
+ }
44
+
45
+ return loose ? decoderLoose.decode(arr) : decoderFatal.decode(arr)
46
+ }
47
+
48
+ export const utf8fromString = (str, format = 'uint8') => typedView(encode(str, false), format)
49
+ export const utf8fromStringLoose = (str, format = 'uint8') => typedView(encode(str, true), format)
50
+ export const utf8toString = (arr) => decode(arr, false)
51
+ 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))