@exodus/bytes 1.0.0-rc.4 → 1.0.0-rc.5
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/assert.js +0 -4
- package/base58.js +212 -0
- package/base58check.js +30 -0
- package/fallback/hex.js +0 -3
- package/hex.js +11 -1
- package/package.json +24 -4
- package/utf8.js +10 -3
package/assert.js
CHANGED
package/base58.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { typedView } from './array.js'
|
|
2
|
+
import { assertUint8 } from './assert.js'
|
|
3
|
+
import { nativeDecoder, nativeEncoder } from './fallback/_utils.js'
|
|
4
|
+
|
|
5
|
+
const alphabet = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
|
|
6
|
+
const codes = new Uint8Array(alphabet.map((x) => x.charCodeAt(0)))
|
|
7
|
+
const ZERO = alphabet[0]
|
|
8
|
+
const zeroC = codes[0]
|
|
9
|
+
|
|
10
|
+
const _0n = BigInt(0)
|
|
11
|
+
const _1n = BigInt(1)
|
|
12
|
+
const _8n = BigInt(8)
|
|
13
|
+
const _32n = BigInt(32)
|
|
14
|
+
const _58n = BigInt(58)
|
|
15
|
+
const _0xffffffffn = BigInt(0xff_ff_ff_ff)
|
|
16
|
+
|
|
17
|
+
let table // 15 * 82, diagonal, <1kb
|
|
18
|
+
let fromMap
|
|
19
|
+
|
|
20
|
+
const E_CHAR = 'Invalid character in base58 input'
|
|
21
|
+
|
|
22
|
+
const shouldUseBigIntFrom = Boolean(globalThis.HermesInternal) // faster only on Hermes, numbers path beats it on normal engines
|
|
23
|
+
|
|
24
|
+
export function toBase58(arr) {
|
|
25
|
+
assertUint8(arr)
|
|
26
|
+
const length = arr.length
|
|
27
|
+
if (length === 0) return ''
|
|
28
|
+
|
|
29
|
+
let zeros = 0
|
|
30
|
+
while (zeros < length && arr[zeros] === 0) zeros++
|
|
31
|
+
|
|
32
|
+
if (length > 60) {
|
|
33
|
+
// Slow path. Can be optimized ~10%, but the main factor is /58n division anyway, so doesn't matter much
|
|
34
|
+
let x = _0n
|
|
35
|
+
for (let i = 0; i < arr.length; i++) x = (x << _8n) | BigInt(arr[i])
|
|
36
|
+
|
|
37
|
+
let out = ''
|
|
38
|
+
while (x) {
|
|
39
|
+
const d = x / _58n
|
|
40
|
+
out = alphabet[Number(x - _58n * d)] + out
|
|
41
|
+
x = d
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return ZERO.repeat(zeros) + out
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// We run fast mode operations only on short (<=60 bytes) inputs, via precomputation table
|
|
48
|
+
if (!table) {
|
|
49
|
+
table = []
|
|
50
|
+
let x = _1n
|
|
51
|
+
for (let i = 0; i < 15; i++) {
|
|
52
|
+
// Convert x to base 58 digits
|
|
53
|
+
const in58 = []
|
|
54
|
+
let y = x
|
|
55
|
+
while (y) {
|
|
56
|
+
const d = y / _58n
|
|
57
|
+
in58.push(Number(y - _58n * d))
|
|
58
|
+
y = d
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
table.push(new Uint8Array(in58))
|
|
62
|
+
x <<= _32n
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const res = []
|
|
67
|
+
{
|
|
68
|
+
let j = 0
|
|
69
|
+
// We group each 4 bytes into 32-bit chunks
|
|
70
|
+
// Not using u32arr to not deal with remainder + BE/LE differences
|
|
71
|
+
for (let i = length - 1; i >= 0; i -= 4) {
|
|
72
|
+
let c
|
|
73
|
+
if (i > 2) {
|
|
74
|
+
c = (arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16) | (arr[i - 3] << 24)) >>> 0
|
|
75
|
+
} else if (i > 1) {
|
|
76
|
+
c = arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16)
|
|
77
|
+
} else {
|
|
78
|
+
c = i === 1 ? arr[i] | (arr[i - 1] << 8) : arr[i]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const row = table[j++]
|
|
82
|
+
if (c === 0) continue
|
|
83
|
+
const olen = res.length
|
|
84
|
+
const nlen = row.length
|
|
85
|
+
let k = 0
|
|
86
|
+
for (; k < olen; k++) res[k] += c * row[k]
|
|
87
|
+
while (k < nlen) res.push(c * row[k++])
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// We can now do a single scan over regular numbers under MAX_SAFE_INTEGER
|
|
92
|
+
// Note: can't use int32 operations on them, as they are outside of 2**32 range
|
|
93
|
+
// This is faster though
|
|
94
|
+
{
|
|
95
|
+
let carry = 0
|
|
96
|
+
let i = 0
|
|
97
|
+
while (i < res.length) {
|
|
98
|
+
const c = res[i] + carry
|
|
99
|
+
carry = Math.floor(c / 58)
|
|
100
|
+
res[i++] = c - carry * 58
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
while (carry) {
|
|
104
|
+
const c = carry
|
|
105
|
+
carry = Math.floor(c / 58)
|
|
106
|
+
res[i++] = c - carry * 58
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (nativeDecoder) {
|
|
111
|
+
const oa = new Uint8Array(res.length)
|
|
112
|
+
let j = 0
|
|
113
|
+
for (let i = res.length - 1; i >= 0; i--) oa[j++] = codes[res[i]]
|
|
114
|
+
return ZERO.repeat(zeros) + nativeDecoder.decode(oa)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let out = ''
|
|
118
|
+
for (let i = res.length - 1; i >= 0; i--) out += alphabet[res[i]]
|
|
119
|
+
return ZERO.repeat(zeros) + out
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// TODO: test on 'z'.repeat(from 1 to smth)
|
|
123
|
+
export function fromBase58(str, format = 'uint8') {
|
|
124
|
+
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
125
|
+
const length = str.length
|
|
126
|
+
if (length === 0) return typedView(new Uint8Array(), format)
|
|
127
|
+
|
|
128
|
+
let zeros = 0
|
|
129
|
+
while (zeros < length && str.charCodeAt(zeros) === zeroC) zeros++
|
|
130
|
+
|
|
131
|
+
if (!fromMap) {
|
|
132
|
+
fromMap = new Int8Array(256).fill(-1)
|
|
133
|
+
for (let i = 0; i < 58; i++) fromMap[alphabet[i].charCodeAt(0)] = i
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const size = zeros + (((length - zeros) * 3 + 1) >> 2) // 3/4 rounded up, larger than ~0.73 coef to fit everything
|
|
137
|
+
const res = new Uint8Array(size)
|
|
138
|
+
let at = size // where is the first significant byte written
|
|
139
|
+
|
|
140
|
+
if (shouldUseBigIntFrom) {
|
|
141
|
+
let x = _0n
|
|
142
|
+
|
|
143
|
+
// nativeEncoder gives a benefit here
|
|
144
|
+
if (nativeEncoder) {
|
|
145
|
+
const codes = nativeEncoder.encode(str)
|
|
146
|
+
if (codes.length !== str.length) throw new SyntaxError(E_CHAR) // non-ascii
|
|
147
|
+
for (let i = zeros; i < length; i++) {
|
|
148
|
+
const c = fromMap[codes[i]]
|
|
149
|
+
if (c < 0) throw new SyntaxError(E_CHAR)
|
|
150
|
+
x = x * _58n + BigInt(c)
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
for (let i = zeros; i < length; i++) {
|
|
154
|
+
const charCode = str.charCodeAt(i)
|
|
155
|
+
const c = fromMap[charCode]
|
|
156
|
+
if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
|
|
157
|
+
x = x * _58n + BigInt(c)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
while (x) {
|
|
162
|
+
let y = Number(x & _0xffffffffn)
|
|
163
|
+
x >>= 32n
|
|
164
|
+
res[--at] = y & 0xff
|
|
165
|
+
y >>>= 8
|
|
166
|
+
if (!x && !y) break
|
|
167
|
+
res[--at] = y & 0xff
|
|
168
|
+
y >>>= 8
|
|
169
|
+
if (!x && !y) break
|
|
170
|
+
res[--at] = y & 0xff
|
|
171
|
+
y >>>= 8
|
|
172
|
+
if (!x && !y) break
|
|
173
|
+
res[--at] = y & 0xff
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
for (let i = zeros; i < length; i++) {
|
|
177
|
+
const charCode = str.charCodeAt(i)
|
|
178
|
+
let c = fromMap[charCode]
|
|
179
|
+
if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
|
|
180
|
+
|
|
181
|
+
let k = size - 1
|
|
182
|
+
for (;;) {
|
|
183
|
+
if (c === 0 && k < at) break
|
|
184
|
+
c += 58 * res[k]
|
|
185
|
+
res[k] = c & 0xff
|
|
186
|
+
c >>>= 8
|
|
187
|
+
k--
|
|
188
|
+
// unroll a bit
|
|
189
|
+
if (c === 0 && k < at) break
|
|
190
|
+
c += 58 * res[k]
|
|
191
|
+
res[k] = c & 0xff
|
|
192
|
+
c >>>= 8
|
|
193
|
+
k--
|
|
194
|
+
if (c === 0 && k < at) break
|
|
195
|
+
c += 58 * res[k]
|
|
196
|
+
res[k] = c & 0xff
|
|
197
|
+
c >>>= 8
|
|
198
|
+
k--
|
|
199
|
+
if (c === 0 && k < at) break
|
|
200
|
+
c += 58 * res[k]
|
|
201
|
+
res[k] = c & 0xff
|
|
202
|
+
c >>>= 8
|
|
203
|
+
k--
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
at = k + 1
|
|
207
|
+
if (c !== 0 || at < zeros) throw new Error('Unexpected') // unreachable
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return typedView(res.slice(at - zeros), format) // slice is faster for small sizes than subarray
|
|
212
|
+
}
|
package/base58check.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { typedView } from './array.js'
|
|
2
|
+
import { assertUint8 } from './assert.js'
|
|
3
|
+
import { toBase58, fromBase58 } from './base58.js'
|
|
4
|
+
import { hashSync } from '@exodus/crypto/hash'
|
|
5
|
+
|
|
6
|
+
// Note: while API is async, we use hashSync for now until we improve webcrypto perf for hash256
|
|
7
|
+
// Inputs to base58 are typically very small, and that makes a difference
|
|
8
|
+
|
|
9
|
+
const hash256 = (x) => hashSync('sha256', hashSync('sha256', x, 'uint8'), 'uint8')
|
|
10
|
+
|
|
11
|
+
const E_CHECKSUM = 'Invalid checksum'
|
|
12
|
+
|
|
13
|
+
export async function toBase58check(arr) {
|
|
14
|
+
assertUint8(arr)
|
|
15
|
+
const checksum = hash256(arr)
|
|
16
|
+
const res = new Uint8Array(arr.length + 4)
|
|
17
|
+
res.set(arr, 0)
|
|
18
|
+
res.set(checksum.subarray(0, 4), arr.length)
|
|
19
|
+
return toBase58(res)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function fromBase58check(str, format = 'uint8') {
|
|
23
|
+
const arr = fromBase58(str) // checks input
|
|
24
|
+
const len4 = arr.length - 4
|
|
25
|
+
const payload = arr.subarray(0, len4)
|
|
26
|
+
const c = arr.subarray(len4)
|
|
27
|
+
const r = hash256(payload)
|
|
28
|
+
if ((c[0] ^ r[0]) | (c[1] ^ r[1]) | (c[2] ^ r[2]) | (c[3] ^ r[3])) throw new Error(E_CHECKSUM)
|
|
29
|
+
return typedView(payload, format)
|
|
30
|
+
}
|
package/fallback/hex.js
CHANGED
|
@@ -58,9 +58,6 @@ export function fromHex(str) {
|
|
|
58
58
|
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
59
59
|
if (str.length % 2 !== 0) throw new SyntaxError(E_HEX)
|
|
60
60
|
|
|
61
|
-
// We don't use native Buffer impl, as rechecking input make it slower than pure js
|
|
62
|
-
// This path is used only on older engines though
|
|
63
|
-
|
|
64
61
|
if (!dehexArray) {
|
|
65
62
|
dehexArray = new Int8Array(mapSize).fill(-1) // no regex input validation here, so we map all other bytes to -1 and recheck sign
|
|
66
63
|
for (let i = 0; i < 16; i++) {
|
package/hex.js
CHANGED
|
@@ -6,6 +6,8 @@ const { Buffer } = globalThis // Buffer is optional, only used when native
|
|
|
6
6
|
const haveNativeBuffer = Buffer && !Buffer.TYPED_ARRAY_SUPPORT
|
|
7
7
|
const { toHex: webHex } = Uint8Array.prototype // Modern engines have this
|
|
8
8
|
|
|
9
|
+
const { E_HEX } = js
|
|
10
|
+
|
|
9
11
|
export function toHex(arr) {
|
|
10
12
|
assertUint8(arr)
|
|
11
13
|
if (arr.length === 0) return ''
|
|
@@ -18,4 +20,12 @@ export function toHex(arr) {
|
|
|
18
20
|
// Unlike Buffer.from(), throws on invalid input
|
|
19
21
|
export const fromHex = Uint8Array.fromHex
|
|
20
22
|
? (str, format = 'uint8') => typedView(Uint8Array.fromHex(str), format)
|
|
21
|
-
:
|
|
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
|
+
}
|
|
31
|
+
: (str, format = 'uint8') => typedView(js.fromHex(str), format)
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/bytes",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.5",
|
|
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:
|
|
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",
|
|
@@ -47,6 +46,8 @@
|
|
|
47
46
|
"/array.js",
|
|
48
47
|
"/assert.js",
|
|
49
48
|
"/base32.js",
|
|
49
|
+
"/base58.js",
|
|
50
|
+
"/base58check.js",
|
|
50
51
|
"/base64.js",
|
|
51
52
|
"/hex.js",
|
|
52
53
|
"/utf8.js"
|
|
@@ -54,23 +55,42 @@
|
|
|
54
55
|
"exports": {
|
|
55
56
|
"./array.js": "./array.js",
|
|
56
57
|
"./base32.js": "./base32.js",
|
|
58
|
+
"./base58.js": "./base58.js",
|
|
59
|
+
"./base58check.js": "./base58check.js",
|
|
57
60
|
"./base64.js": "./base64.js",
|
|
58
61
|
"./hex.js": "./hex.js",
|
|
59
62
|
"./utf8.js": "./utf8.js"
|
|
60
63
|
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@exodus/crypto": "^1.0.0-rc.4"
|
|
66
|
+
},
|
|
67
|
+
"peerDependenciesMeta": {
|
|
68
|
+
"@exodus/crypto": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
71
|
+
},
|
|
61
72
|
"devDependencies": {
|
|
73
|
+
"@ethersproject/strings": "^5.8.0",
|
|
74
|
+
"@exodus/crypto": "1.0.0-rc.29",
|
|
62
75
|
"@exodus/eslint-config": "^5.24.0",
|
|
63
76
|
"@exodus/prettier": "^1.0.0",
|
|
64
|
-
"@exodus/test": "^1.0.0-rc.
|
|
77
|
+
"@exodus/test": "^1.0.0-rc.107",
|
|
78
|
+
"@noble/hashes": "^2.0.1",
|
|
65
79
|
"@scure/base": "^1.2.6",
|
|
80
|
+
"@stablelib/base64": "^2.0.1",
|
|
81
|
+
"@stablelib/hex": "^2.0.1",
|
|
66
82
|
"@types/node": "^24.0.10",
|
|
67
83
|
"base-x": "^5.0.1",
|
|
68
84
|
"base32.js": "^0.1.0",
|
|
69
85
|
"base64-js": "^1.5.1",
|
|
86
|
+
"bs58": "^6.0.0",
|
|
87
|
+
"bs58check": "^4.0.0",
|
|
88
|
+
"bstring": "^0.3.9",
|
|
70
89
|
"buffer": "^6.0.3",
|
|
71
90
|
"electron": "36.5.0",
|
|
72
91
|
"eslint": "^8.44.0",
|
|
73
92
|
"fast-base64-decode": "^2.0.0",
|
|
93
|
+
"fast-base64-encode": "^1.0.0",
|
|
74
94
|
"hi-base32": "^0.5.1",
|
|
75
95
|
"jsvu": "^3.0.0",
|
|
76
96
|
"text-encoding": "^0.7.0"
|
package/utf8.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertUint8 } from './assert.js'
|
|
2
2
|
import { typedView } from './array.js'
|
|
3
3
|
import * as js from './fallback/utf8.js'
|
|
4
4
|
|
|
@@ -11,6 +11,7 @@ const nativeEncoder = isNative(TextEncoder) ? new TextEncoder() : null
|
|
|
11
11
|
// We don't want to strip anything unexpectedly
|
|
12
12
|
const decoderFatal = haveDecoder ? new TextDecoder('utf8', { ignoreBOM: true, fatal: true }) : null
|
|
13
13
|
const decoderLoose = haveDecoder ? new TextDecoder('utf8', { ignoreBOM: true }) : null
|
|
14
|
+
const { isWellFormed } = String.prototype
|
|
14
15
|
|
|
15
16
|
const { E_STRICT, E_STRICT_UNICODE } = js
|
|
16
17
|
|
|
@@ -18,6 +19,12 @@ const shouldUseEscapePath = Boolean(globalThis.HermesInternal) // faster only on
|
|
|
18
19
|
|
|
19
20
|
function deLoose(str, loose, res) {
|
|
20
21
|
if (loose) return res
|
|
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
|
+
|
|
21
28
|
// Recheck if the string was encoded correctly
|
|
22
29
|
let start = 0
|
|
23
30
|
const last = res.length - 2
|
|
@@ -28,7 +35,7 @@ function deLoose(str, loose, res) {
|
|
|
28
35
|
start = pos + 1
|
|
29
36
|
if (res[pos + 1] === 0xbf && res[pos + 2] === 0xbd) {
|
|
30
37
|
// Found a replacement char in output, need to recheck if we encoded the input correctly
|
|
31
|
-
|
|
38
|
+
if (str !== decode(res)) throw new TypeError(E_STRICT_UNICODE)
|
|
32
39
|
return res
|
|
33
40
|
}
|
|
34
41
|
}
|
|
@@ -37,7 +44,7 @@ function deLoose(str, loose, res) {
|
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
function encode(str, loose = false) {
|
|
40
|
-
|
|
47
|
+
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
41
48
|
if (haveNativeBuffer) return deLoose(str, loose, Buffer.from(str)) // faster on ascii on Node.js
|
|
42
49
|
if (nativeEncoder) return deLoose(str, loose, nativeEncoder.encode(str)) // Node.js, browsers, and Hermes
|
|
43
50
|
// No reason to use unescape + encodeURIComponent: it's slower than JS on normal engines, and modern Hermes already has TextEncoder
|