@exodus/bytes 1.0.0-rc.0 → 1.0.0-rc.10

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/base64.js CHANGED
@@ -1,204 +1,177 @@
1
- import { assert, assertUint8 } from './assert.js'
2
- import { fromTypedArray } from './array.js'
1
+ import { assertUint8, assertEmptyRest } from './assert.js'
2
+ import { typedView } from './array.js'
3
+ import { isHermes, skipWeb } from './fallback/_utils.js'
4
+ import { decodeLatin1, encodeLatin1 } from './fallback/latin1.js'
5
+ import * as js from './fallback/base64.js'
3
6
 
4
7
  // See https://datatracker.ietf.org/doc/html/rfc4648
5
8
 
6
- // base64: A-Za-z0-9+/ and =
7
- // base64url: A-Za-z0-9_-
9
+ // base64: A-Za-z0-9+/ and = if padding not disabled
10
+ // base64url: A-Za-z0-9_- and = if padding enabled
8
11
 
9
- const { Buffer, atob } = globalThis // Buffer is optional, only used when native
12
+ const { Buffer, atob, btoa } = globalThis // Buffer is optional, only used when native
10
13
  const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
11
14
  const { toBase64: web64 } = Uint8Array.prototype // Modern engines have this
12
15
 
13
- export function toBase64(x) {
16
+ const { E_CHAR, E_PADDING, E_LENGTH, E_LAST } = js
17
+
18
+ // faster only on Hermes (and a little in old Chrome), js path beats it on normal engines
19
+ const shouldUseBtoa = btoa && isHermes
20
+ const shouldUseAtob = atob && isHermes
21
+
22
+ // For native Buffer codepaths only
23
+ const isBuffer = (x) => x.constructor === Buffer && Buffer.isBuffer(x)
24
+ const toBuffer = (x) => (isBuffer(x) ? x : Buffer.from(x.buffer, x.byteOffset, x.byteLength))
25
+
26
+ function maybeUnpad(res, padding) {
27
+ if (padding) return res
28
+ const at = res.indexOf('=', res.length - 3)
29
+ return at === -1 ? res : res.slice(0, at)
30
+ }
31
+
32
+ function maybePad(res, padding) {
33
+ return padding && res.length % 4 !== 0 ? res + '='.repeat(4 - (res.length % 4)) : res
34
+ }
35
+
36
+ const toUrl = (x) => x.replaceAll('+', '-').replaceAll('/', '_')
37
+ const haveWeb = (x) => !skipWeb && web64 && x.toBase64 === web64
38
+
39
+ export function toBase64(x, { padding = true } = {}) {
14
40
  assertUint8(x)
15
- if (web64 && x.toBase64 === web64) return x.toBase64() // Modern
16
- if (!haveNativeBuffer) return toBase64js(x, BASE64, true) // Fallback
17
- if (x.constructor === Buffer && Buffer.isBuffer(x)) return x.toString('base64') // Older Node.js
18
- return Buffer.from(x.buffer, x.byteOffset, x.byteLength).toString('base64') // Older Node.js
41
+ if (haveWeb(x)) return padding ? x.toBase64() : x.toBase64({ omitPadding: !padding }) // Modern, optionless is slightly faster
42
+ if (haveNativeBuffer) return maybeUnpad(toBuffer(x).base64Slice(0, x.byteLength), padding) // Older Node.js
43
+ if (shouldUseBtoa) return maybeUnpad(btoa(decodeLatin1(x)), padding)
44
+ return js.toBase64(x, false, padding) // Fallback
19
45
  }
20
46
 
21
- // NOTE: base64url omits padding
22
- export function toBase64url(x) {
47
+ // NOTE: base64url omits padding by default
48
+ export function toBase64url(x, { padding = false } = {}) {
23
49
  assertUint8(x)
24
- if (web64 && x.toBase64 === web64) return x.toBase64({ alphabet: 'base64url', omitPadding: true }) // Modern
25
- if (!haveNativeBuffer) return toBase64js(x, BASE64URL, false) // Fallback
26
- if (x.constructor === Buffer && Buffer.isBuffer(x)) return x.toString('base64url') // Older Node.js
27
- return Buffer.from(x.buffer, x.byteOffset, x.byteLength).toString('base64url') // Older Node.js
50
+ if (haveWeb(x)) return x.toBase64({ alphabet: 'base64url', omitPadding: !padding }) // Modern
51
+ if (haveNativeBuffer) return maybePad(toBuffer(x).base64urlSlice(0, x.byteLength), padding) // Older Node.js
52
+ if (shouldUseBtoa) return maybeUnpad(toUrl(btoa(decodeLatin1(x))), padding)
53
+ return js.toBase64(x, true, padding) // Fallback
28
54
  }
29
55
 
30
56
  // Unlike Buffer.from(), throws on invalid input (non-base64 symbols and incomplete chunks)
31
57
  // Unlike Buffer.from() and Uint8Array.fromBase64(), does not allow spaces
32
58
  // NOTE: Always operates in strict mode for last chunk
33
59
 
34
- // Accepts both padded and non-padded variants, only strict base64
35
- export function fromBase64(str, format = 'uint8') {
36
- if (typeof str !== 'string') throw new TypeError('Input is not a string')
60
+ // By default accepts both padded and non-padded variants, only strict base64
61
+ export function fromBase64(str, options) {
62
+ if (typeof options === 'string') options = { format: options } // Compat due to usage, TODO: remove
63
+ if (!options) return fromBase64common(str, false, 'both', 'uint8', null)
64
+ const { format = 'uint8', padding = 'both', ...rest } = options
65
+ return fromBase64common(str, false, padding, format, rest)
66
+ }
37
67
 
38
- // These checks should be needed only for Buffer path, not Uint8Array.fromBase64 path, but JSC lacks proper checks
39
- assert(str.length % 4 !== 1, 'Invalid base64 length') // JSC misses this in fromBase64
40
- if (str.endsWith('=')) {
41
- assert(str.length % 4 === 0, 'Invalid padded length') // JSC misses this too
42
- assert(str[str.length - 3] !== '=', 'Excessive padding') // no more than two = at the end
43
- }
68
+ // By default accepts only non-padded strict base64url
69
+ export function fromBase64url(str, options) {
70
+ if (!options) return fromBase64common(str, true, false, 'uint8', null)
71
+ const { format = 'uint8', padding = false, ...rest } = options
72
+ return fromBase64common(str, true, padding, format, rest)
73
+ }
44
74
 
45
- return fromTypedArray(fromBase64common(str, false), format)
75
+ // By default accepts both padded and non-padded variants, base64 or base64url
76
+ export function fromBase64any(str, { format = 'uint8', padding = 'both', ...rest } = {}) {
77
+ const isBase64url = !str.includes('+') && !str.includes('/') // likely to fail fast, as most input is non-url, also double scan is faster than regex
78
+ return fromBase64common(str, isBase64url, padding, format, rest)
46
79
  }
47
80
 
48
- // Accepts both only non-padded strict base64url
49
- export function fromBase64url(str, format = 'uint8') {
81
+ function fromBase64common(str, isBase64url, padding, format, rest) {
50
82
  if (typeof str !== 'string') throw new TypeError('Input is not a string')
83
+ if (rest !== null) assertEmptyRest(rest)
84
+ const auto = padding === 'both' ? str.endsWith('=') : undefined
85
+ // Older JSC supporting Uint8Array.fromBase64 lacks proper checks
86
+ if (padding === true || auto === true) {
87
+ if (str.length % 4 !== 0) throw new SyntaxError(E_PADDING) // JSC misses this
88
+ if (str[str.length - 3] === '=') throw new SyntaxError(E_PADDING) // no more than two = at the end
89
+ } else if (padding === false || auto === false) {
90
+ if (str.length % 4 === 1) throw new SyntaxError(E_LENGTH) // JSC misses this in fromBase64
91
+ if (padding === false && str.endsWith('=')) {
92
+ throw new SyntaxError('Did not expect padding in base64 input') // inclusion is checked separately
93
+ }
94
+ } else {
95
+ throw new TypeError('Invalid padding option')
96
+ }
51
97
 
52
- // These checks should be needed only for Buffer path, not Uint8Array.fromBase64 path, but JSC lacks proper checks
53
- assert(str.length % 4 !== 1, 'Invalid base64 length') // JSC misses this in fromBase64
54
- assert(!str.endsWith('='), 'Did not expect padding in base64url input') // inclusion is checked separately
98
+ return typedView(fromBase64impl(str, isBase64url, padding), format)
99
+ }
100
+
101
+ // ASCII whitespace is U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, or U+0020 SPACE
102
+ const ASCII_WHITESPACE = /[\t\n\f\r ]/ // non-u for JSC perf
55
103
 
56
- return fromTypedArray(fromBase64common(str, true), format)
104
+ function noWhitespaceSeen(str, arr) {
105
+ const at = str.indexOf('=', str.length - 3)
106
+ const paddingLength = at >= 0 ? str.length - at : 0
107
+ const chars = str.length - paddingLength
108
+ const e = chars % 4 // extra chars past blocks of 4
109
+ const b = arr.length - ((chars - e) / 4) * 3 // remaining bytes not covered by full blocks of chars
110
+ return (e === 0 && b === 0) || (e === 2 && b === 1) || (e === 3 && b === 2)
57
111
  }
58
112
 
59
- let fromBase64common
60
- if (Uint8Array.fromBase64) {
113
+ let fromBase64impl
114
+ if (!skipWeb && Uint8Array.fromBase64) {
61
115
  // NOTICE: this is actually slower than our JS impl in older JavaScriptCore and (slightly) in SpiderMonkey, but faster on V8 and new JavaScriptCore
62
- fromBase64common = (str, isBase64url) => {
116
+ fromBase64impl = (str, isBase64url, padding) => {
63
117
  const alphabet = isBase64url ? 'base64url' : 'base64'
64
- assert(!/\s/u.test(str), `Invalid character in ${alphabet} input`) // all other chars are checked natively
65
- const padded = str.length % 4 > 0 ? `${str}${'='.repeat(4 - (str.length % 4))}` : str
66
- return Uint8Array.fromBase64(padded, { alphabet, lastChunkHandling: 'strict' })
67
- }
68
- } else {
69
- fromBase64common = (str, isBase64url) => {
70
- if (isBase64url) {
71
- assert(!/[^0-9a-z_-]/iu.test(str), 'Invalid character in base64url input')
72
- } else {
73
- assert(!/[^0-9a-z=+/]/iu.test(str), 'Invalid character in base64 input')
74
- }
75
118
 
76
119
  let arr
77
- if (!haveNativeBuffer && atob) {
78
- // atob is faster than manual parsing on Hermes
79
- const raw = atob(isBase64url ? str.replaceAll('-', '+').replaceAll('_', '/') : str)
80
- arr = new Uint8Array(raw.length)
81
- for (let i = 0; i < raw.length; i++) arr[i] = raw.charCodeAt(i)
120
+ if (padding === true) {
121
+ // Padding is required from user, and we already checked that string length is divisible by 4
122
+ // Padding might still be wrong due to whitespace, but in that case native impl throws expected error
123
+ arr = Uint8Array.fromBase64(str, { alphabet, lastChunkHandling: 'strict' })
82
124
  } else {
83
- // base64url is already checked to have no padding via a regex above
84
- if (!isBase64url) {
85
- const at = str.indexOf('=')
86
- if (at >= 0) assert(!/[^=]/iu.test(str.slice(at)), 'Invalid padding')
125
+ try {
126
+ const padded = str.length % 4 > 0 ? `${str}${'='.repeat(4 - (str.length % 4))}` : str
127
+ arr = Uint8Array.fromBase64(padded, { alphabet, lastChunkHandling: 'strict' })
128
+ } catch (err) {
129
+ // Normalize error: whitespace in input could have caused added padding to be invalid
130
+ // But reporting that as a padding error would be confusing
131
+ throw ASCII_WHITESPACE.test(str) ? new SyntaxError(E_CHAR) : err
87
132
  }
88
-
89
- arr = haveNativeBuffer ? Buffer.from(str, 'base64') : fromBase64js(str)
90
- }
91
-
92
- if (arr.length % 3 !== 0) {
93
- // Check last chunk to be strict if it was incomplete
94
- const expected = toBase64(arr.subarray(-(arr.length % 3)))
95
- const end = str.length % 4 === 0 ? str.slice(-4) : str.slice(-(str.length % 4)).padEnd(4, '=')
96
- const actual = isBase64url ? end.replaceAll('-', '+').replaceAll('_', '/') : end
97
- if (expected !== actual) throw new Error('Invalid last chunk')
98
133
  }
99
134
 
135
+ // We don't allow whitespace in input, but that can be rechecked based on output length
136
+ // All other chars are checked natively
137
+ if (!noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR)
100
138
  return arr
101
139
  }
102
- }
103
-
104
- const BASE64 = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/']
105
- const BASE64URL = [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_']
106
-
107
- const BASE64_PAIRS = []
108
- const BASE64URL_PAIRS = []
109
-
110
- // We construct output by concatenating chars, this seems to be fine enough on modern JS engines
111
- function toBase64js(arr, alphabet, padding) {
112
- assertUint8(arr)
113
- const fullChunks = Math.floor(arr.length / 3)
114
- const fullChunksBytes = fullChunks * 3
115
- let o = ''
116
- let i = 0
117
-
118
- const pairs = alphabet === BASE64URL ? BASE64URL_PAIRS : BASE64_PAIRS
119
- if (pairs.length === 0) {
120
- for (let i = 0; i < 64; i++) {
121
- for (let j = 0; j < 64; j++) pairs.push(`${alphabet[i]}${alphabet[j]}`)
122
- }
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
123
148
  }
124
-
125
- // Fast path for complete blocks
126
- // This whole loop can be commented out, the algorithm won't change, it's just an optimization of the next loop
127
- for (; i < fullChunksBytes; i += 3) {
128
- const a = arr[i]
129
- const b = arr[i + 1]
130
- const c = arr[i + 2]
131
- o += pairs[(a << 4) | (b >> 4)] + pairs[((b & 0x0f) << 8) | c]
132
- }
133
-
134
- // If we have something left, process it with a full algo
135
- let carry = 0
136
- let shift = 2 // First byte needs to be shifted by 2 to get 6 bits
137
- const length = arr.length
138
- for (; i < length; i++) {
139
- const x = arr[i]
140
- o += alphabet[carry | (x >> shift)] // shift >= 2, so this fits
141
- if (shift === 6) {
142
- shift = 0
143
- o += alphabet[x & 0x3f]
149
+ } else if (shouldUseAtob) {
150
+ // atob is faster than manual parsing on Hermes
151
+ fromBase64impl = (str, isBase64url, padding) => {
152
+ let arr
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
144
156
  }
145
157
 
146
- carry = (x << (6 - shift)) & 0x3f
147
- shift += 2 // Each byte prints 6 bits and leaves 2 bits
148
- }
149
-
150
- if (shift !== 2) o += alphabet[carry] // shift 2 means we have no carry left
151
- if (padding) o += ['', '==', '='][length - fullChunksBytes]
152
-
153
- return o
154
- }
158
+ try {
159
+ arr = encodeLatin1(atob(str))
160
+ } catch {
161
+ throw new SyntaxError(E_CHAR) // convert atob errors
162
+ }
155
163
 
156
- // Assumes no chars after =, checked
157
- let fromBase64jsMap
164
+ if (!isBase64url && !noWhitespaceSeen(str, arr)) throw new SyntaxError(E_CHAR) // base64url checks input above
158
165
 
159
- function fromBase64js(str) {
160
- const map = fromBase64jsMap || new Array(256)
161
- if (!fromBase64jsMap) {
162
- fromBase64jsMap = map
163
- BASE64.forEach((c, i) => (map[c.charCodeAt(0)] = i))
164
- map['-'.charCodeAt(0)] = map['+'.charCodeAt(0)] // for base64url
165
- map['_'.charCodeAt(0)] = map['/'.charCodeAt(0)] // for base64url
166
- }
167
-
168
- let inputLength = str.length
169
- while (str[inputLength - 1] === '=') inputLength--
170
-
171
- const arr = new Uint8Array(Math.floor((inputLength * 3) / 4))
172
- const tailLength = inputLength % 4
173
- const mainLength = inputLength - tailLength // multiples of 4
174
-
175
- let at = 0
176
- let i = 0
177
- let tmp
178
-
179
- while (i < mainLength) {
180
- tmp =
181
- (map[str.charCodeAt(i)] << 18) |
182
- (map[str.charCodeAt(i + 1)] << 12) |
183
- (map[str.charCodeAt(i + 2)] << 6) |
184
- map[str.charCodeAt(i + 3)]
185
- arr[at++] = tmp >> 16
186
- arr[at++] = (tmp >> 8) & 0xff
187
- arr[at++] = tmp & 0xff
188
- i += 4
189
- }
166
+ if (arr.length % 3 !== 0) {
167
+ // Check last chunk to be strict if it was incomplete
168
+ const expected = toBase64(arr.subarray(-(arr.length % 3))) // str is normalized to non-url already
169
+ const end = str.length % 4 === 0 ? str.slice(-4) : str.slice(-(str.length % 4)).padEnd(4, '=')
170
+ if (expected !== end) throw new SyntaxError(E_LAST)
171
+ }
190
172
 
191
- if (tailLength === 3) {
192
- tmp =
193
- (map[str.charCodeAt(i)] << 10) |
194
- (map[str.charCodeAt(i + 1)] << 4) |
195
- (map[str.charCodeAt(i + 2)] >> 2)
196
- arr[at++] = (tmp >> 8) & 0xff
197
- arr[at++] = tmp & 0xff
198
- } else if (tailLength === 2) {
199
- tmp = (map[str.charCodeAt(i)] << 2) | (map[str.charCodeAt(i + 1)] >> 4)
200
- arr[at++] = tmp & 0xff
173
+ return arr
201
174
  }
202
-
203
- return arr
175
+ } else {
176
+ fromBase64impl = (str, isBase64url, padding) => js.fromBase64(str, isBase64url) // validated in js
204
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)
@@ -0,0 +1,7 @@
1
+ export {
2
+ TextDecoder,
3
+ TextEncoder,
4
+ normalizeEncoding,
5
+ getBOMEncoding,
6
+ legacyHookDecode,
7
+ } from './fallback/encoding.js'
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'