@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/README.md +172 -2
- package/array.js +1 -1
- package/assert.js +10 -6
- package/base32.js +40 -0
- package/base58.js +220 -0
- package/base58check.js +69 -0
- package/base64.js +133 -161
- package/bech32.js +254 -0
- package/encoding-lite.js +7 -0
- package/encoding.js +12 -0
- package/fallback/_utils.js +126 -0
- package/fallback/base32.js +233 -0
- package/fallback/base64.js +192 -0
- package/fallback/encoding.js +289 -0
- package/fallback/encoding.labels.js +46 -0
- package/fallback/encoding.util.js +34 -0
- package/fallback/hex.js +127 -0
- package/fallback/latin1.js +120 -0
- package/fallback/multi-byte.encodings.cjs +1 -0
- package/fallback/multi-byte.encodings.json +545 -0
- package/fallback/multi-byte.js +448 -0
- package/fallback/multi-byte.table.js +114 -0
- package/fallback/single-byte.encodings.js +45 -0
- package/fallback/single-byte.js +83 -0
- package/fallback/utf16.js +180 -0
- package/fallback/utf8.js +245 -0
- package/hex.js +12 -71
- package/hex.node.js +28 -0
- package/multi-byte.js +13 -0
- package/multi-byte.node.js +25 -0
- package/package.json +106 -14
- package/single-byte.js +55 -0
- package/single-byte.node.js +62 -0
- package/utf16.js +73 -0
- package/utf16.node.js +79 -0
- package/utf8.js +80 -0
- package/utf8.node.js +54 -0
- package/wif.js +42 -0
package/README.md
CHANGED
|
@@ -1,3 +1,173 @@
|
|
|
1
|
-
# bytes
|
|
1
|
+
# `@exodus/bytes`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`Uint8Array` conversion to and from `base64`, `base32`, `base58`, `hex`, `utf8`, `utf16`, `bech32` and `wif`
|
|
4
|
+
|
|
5
|
+
And a [`TextEncoder` / `TextDecoder` polyfill](#textencoder--textdecoder-polyfill)
|
|
6
|
+
|
|
7
|
+
## Strict
|
|
8
|
+
|
|
9
|
+
Performs proper input validation, ensures no garbage-in-garbage-out
|
|
10
|
+
|
|
11
|
+
Tested on Node.js, Deno, Bun, browsers (including Servo), Hermes, QuickJS and barebone engines in CI [(how?)](https://github.com/ExodusMovement/test#exodustest)
|
|
12
|
+
|
|
13
|
+
## Fast
|
|
14
|
+
|
|
15
|
+
* `10-20x` faster than `Buffer` polyfill
|
|
16
|
+
* `2-10x` faster than `iconv-lite`
|
|
17
|
+
|
|
18
|
+
The above was for the js fallback
|
|
19
|
+
|
|
20
|
+
It's up to `100x` when native impl is available \
|
|
21
|
+
e.g. in `utf8fromString` on Hermes / React Native or `fromHex` in Chrome
|
|
22
|
+
|
|
23
|
+
Also:
|
|
24
|
+
* `3-8x` faster than `bs58`
|
|
25
|
+
* `10-30x` faster than `@scure/base` (or `>100x` on Node.js <25)
|
|
26
|
+
* Faster in `utf8toString` / `utf8fromString` than `Buffer` or `TextDecoder` / `TextEncoder` on Node.js
|
|
27
|
+
|
|
28
|
+
See [Performance](./Performance.md) for more info
|
|
29
|
+
|
|
30
|
+
## TextEncoder / TextDecoder polyfill
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding.js'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Less than half the bundle size of [text-encoding](https://npmjs.com/text-encoding), [whatwg-encoding](https://npmjs.com/whatwg-encoding) or [iconv-lite](https://npmjs.com/iconv-lite) (gzipped or not), and [is much faster](#fast).
|
|
37
|
+
See also [lite version](#lite-version).
|
|
38
|
+
|
|
39
|
+
Spec compliant, passing WPT and covered with extra tests.
|
|
40
|
+
|
|
41
|
+
Moreover, tests for this library uncovered [bugs in all major implementations](https://docs.google.com/spreadsheets/d/1pdEefRG6r9fZy61WHGz0TKSt8cO4ISWqlpBN5KntIvQ/edit).
|
|
42
|
+
|
|
43
|
+
[Faster than Node.js native implementation on Node.js](https://github.com/nodejs/node/issues/61041#issuecomment-3649242024).
|
|
44
|
+
|
|
45
|
+
### Caveat: `TextDecoder` / `TextEncoder` APIs are lossy by default per spec
|
|
46
|
+
|
|
47
|
+
_These are only provided as a compatibility layer, prefer hardened APIs instead in new code._
|
|
48
|
+
|
|
49
|
+
* `TextDecoder` can (and should) be used with `{ fatal: true }` option for all purposes demanding correctness / lossless transforms
|
|
50
|
+
|
|
51
|
+
* `TextEncoder` does not support a fatal mode per spec, it always performs replacement.
|
|
52
|
+
|
|
53
|
+
That is not suitable for hashing, cryptography or consensus applications.\
|
|
54
|
+
Otherwise there would be non-equal strings with equal signatures and hashes — the collision is caused by the lossy transform of a JS string to bytes.
|
|
55
|
+
Those also survive e.g. `JSON.stringify`/`JSON.parse` or being sent over network.
|
|
56
|
+
|
|
57
|
+
Use strict APIs in new applications, see `utf8fromString` / `utf16fromString` below.\
|
|
58
|
+
Those throw on non-well-formed strings by default.
|
|
59
|
+
|
|
60
|
+
### Lite version
|
|
61
|
+
|
|
62
|
+
If you don't need support for legacy multi-byte encodings, you can use the lite import:
|
|
63
|
+
```js
|
|
64
|
+
import { TextDecoder, TextEncoder } from '@exodus/bytes/encoding-lite.js'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This reduces the bundle size 9x:\
|
|
68
|
+
from 91 KiB gzipped for `@exodus/bytes/encoding.js` to 10 KiB gzipped for `@exodus/bytes/encoding-lite.js`.\
|
|
69
|
+
(For comparison, `text-encoding` module is 190 KiB gzipped, and `iconv-lite` is 194 KiB gzipped).
|
|
70
|
+
|
|
71
|
+
It still supports `utf-8`, `utf-16le`, `utf-16be` and all single-byte encodings specified by the spec,
|
|
72
|
+
the only difference is support for legacy multi-byte encodings.
|
|
73
|
+
|
|
74
|
+
See [the list of encodings](https://encoding.spec.whatwg.org/#names-and-labels).
|
|
75
|
+
|
|
76
|
+
## API
|
|
77
|
+
|
|
78
|
+
### `@exodus/bytes/utf8.js`
|
|
79
|
+
|
|
80
|
+
##### `utf8fromString(str, format = 'uint8')`
|
|
81
|
+
##### `utf8fromStringLoose(str, format = 'uint8')`
|
|
82
|
+
##### `utf8toString(arr)`
|
|
83
|
+
##### `utf8toStringLoose(arr)`
|
|
84
|
+
|
|
85
|
+
### `@exodus/bytes/utf16.js`
|
|
86
|
+
|
|
87
|
+
##### `utf16fromString(str, format = 'uint16')`
|
|
88
|
+
##### `utf16fromStringLoose(str, format = 'uint16')`
|
|
89
|
+
##### `utf16toString(arr, 'uint16')`
|
|
90
|
+
##### `utf16toStringLoose(arr, 'uint16')`
|
|
91
|
+
|
|
92
|
+
### `@exodus/bytes/single-byte.js`
|
|
93
|
+
|
|
94
|
+
##### `createSinglebyteDecoder(encoding, loose = false)`
|
|
95
|
+
|
|
96
|
+
Create a decoder for a supported one-byte `encoding`.
|
|
97
|
+
|
|
98
|
+
Returns a function `decode(arr)` that decodes bytes to a string.
|
|
99
|
+
|
|
100
|
+
### `@exodus/bytes/multi-byte.js`
|
|
101
|
+
|
|
102
|
+
##### `createMultibyteDecoder(encoding, loose = false)`
|
|
103
|
+
|
|
104
|
+
Create a decoder for a supported legacy multi-byte `encoding`.
|
|
105
|
+
|
|
106
|
+
Returns a function `decode(arr, stream = false)` that decodes bytes to a string.
|
|
107
|
+
|
|
108
|
+
That function will have state while `stream = true` is used.
|
|
109
|
+
|
|
110
|
+
##### `windows1252toString(arr)`
|
|
111
|
+
|
|
112
|
+
Decode `windows-1252` bytes to a string.
|
|
113
|
+
|
|
114
|
+
Also supports `ascii` and `latin-1` as those are strict subsets of `windows-1252`.
|
|
115
|
+
|
|
116
|
+
There is no loose variant for this encoding, all bytes can be decoded.
|
|
117
|
+
|
|
118
|
+
Same as `windows1252toString = createSinglebyteDecoder('windows-1252')`.
|
|
119
|
+
|
|
120
|
+
### `@exodus/bytes/hex.js`
|
|
121
|
+
|
|
122
|
+
##### `toHex(arr)`
|
|
123
|
+
##### `fromHex(string)`
|
|
124
|
+
|
|
125
|
+
### `@exodus/bytes/base64.js`
|
|
126
|
+
|
|
127
|
+
##### `toBase64(arr, { padding = true })`
|
|
128
|
+
##### `toBase64url(arr, { padding = false })`
|
|
129
|
+
##### `fromBase64(str, { format = 'uint8', padding = 'both' })`
|
|
130
|
+
##### `fromBase64url(str, { format = 'uint8', padding = false })`
|
|
131
|
+
##### `fromBase64any(str, { format = 'uint8', padding = 'both' })`
|
|
132
|
+
|
|
133
|
+
### `@exodus/bytes/base32.js`
|
|
134
|
+
|
|
135
|
+
##### `toBase32(arr, { padding = false })`
|
|
136
|
+
##### `toBase32hex(arr, { padding = false })`
|
|
137
|
+
##### `fromBase32(str, { format = 'uint8', padding = 'both' })`
|
|
138
|
+
##### `fromBase32hex(str, { format = 'uint8', padding = 'both' })`
|
|
139
|
+
|
|
140
|
+
### `@exodus/bytes/bech32.js`
|
|
141
|
+
|
|
142
|
+
##### `getPrefix(str, limit = 90)`
|
|
143
|
+
##### `toBech32(prefix, bytes, limit = 90)`
|
|
144
|
+
##### `fromBech32(str, limit = 90)`
|
|
145
|
+
##### `toBech32m(prefix, bytes, limit = 90)`
|
|
146
|
+
##### `fromBech32m(str, limit = 90)`
|
|
147
|
+
|
|
148
|
+
### `@exodus/bytes/base58.js`
|
|
149
|
+
|
|
150
|
+
##### `toBase58(arr)`
|
|
151
|
+
##### `fromBase58(str, format = 'uint8')`
|
|
152
|
+
|
|
153
|
+
##### `toBase58xrp(arr)`
|
|
154
|
+
##### `fromBase58xrp(str, format = 'uint8')`
|
|
155
|
+
|
|
156
|
+
### `@exodus/bytes/base58check.js`
|
|
157
|
+
|
|
158
|
+
##### `async toBase58check(arr)`
|
|
159
|
+
##### `toBase58checkSync(arr)`
|
|
160
|
+
##### `async fromBase58check(str, format = 'uint8')`
|
|
161
|
+
##### `fromBase58checkSync(str, format = 'uint8')`
|
|
162
|
+
##### `makeBase58check(hashAlgo, hashAlgoSync)`
|
|
163
|
+
|
|
164
|
+
### `@exodus/bytes/wif.js`
|
|
165
|
+
|
|
166
|
+
##### `async fromWifString(string, version)`
|
|
167
|
+
##### `fromWifStringSync(string, version)`
|
|
168
|
+
##### `async toWifString({ version, privateKey, compressed })`
|
|
169
|
+
##### `toWifStringSync({ version, privateKey, compressed })`
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
[MIT](./LICENSE)
|
package/array.js
CHANGED
package/assert.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
export function assert(x, msg) {
|
|
2
|
-
if (!x) throw new Error(msg || 'Assertion failed')
|
|
3
|
-
}
|
|
4
|
-
|
|
5
1
|
export function assertEmptyRest(rest) {
|
|
6
2
|
if (Object.keys(rest).length > 0) throw new TypeError('Unexpected extra options')
|
|
7
3
|
}
|
|
@@ -12,10 +8,18 @@ const makeMessage = (name, extra) => `Expected${name ? ` ${name} to be` : ''} an
|
|
|
12
8
|
const TypedArray = Object.getPrototypeOf(Uint8Array)
|
|
13
9
|
|
|
14
10
|
export function assertTypedArray(arr) {
|
|
15
|
-
|
|
11
|
+
if (arr instanceof TypedArray) return
|
|
12
|
+
throw new TypeError('Expected a TypedArray instance')
|
|
16
13
|
}
|
|
17
14
|
|
|
18
|
-
export function assertUint8(arr,
|
|
15
|
+
export function assertUint8(arr, options) {
|
|
16
|
+
if (!options) {
|
|
17
|
+
// fast path
|
|
18
|
+
if (arr instanceof Uint8Array) return
|
|
19
|
+
throw new TypeError('Expected an Uint8Array')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { name, length, ...rest } = options
|
|
19
23
|
assertEmptyRest(rest)
|
|
20
24
|
if (arr instanceof Uint8Array && (length === undefined || arr.length === length)) return
|
|
21
25
|
throw new TypeError(makeMessage(name, length === undefined ? '' : ` of size ${Number(length)}`))
|
package/base32.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { assertEmptyRest } from './assert.js'
|
|
2
|
+
import { typedView } from './array.js'
|
|
3
|
+
import * as js from './fallback/base32.js'
|
|
4
|
+
|
|
5
|
+
// See https://datatracker.ietf.org/doc/html/rfc4648
|
|
6
|
+
|
|
7
|
+
// 8 chars per 5 bytes
|
|
8
|
+
|
|
9
|
+
const { E_PADDING } = js
|
|
10
|
+
|
|
11
|
+
export const toBase32 = (arr, { padding = false } = {}) => js.toBase32(arr, false, padding)
|
|
12
|
+
export const toBase32hex = (arr, { padding = false } = {}) => js.toBase32(arr, true, padding)
|
|
13
|
+
|
|
14
|
+
// By default, valid padding is accepted but not required
|
|
15
|
+
export function fromBase32(str, options) {
|
|
16
|
+
if (!options) return fromBase32common(str, false, 'both', 'uint8', null)
|
|
17
|
+
const { format = 'uint8', padding = 'both', ...rest } = options
|
|
18
|
+
return fromBase32common(str, false, padding, format, rest)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function fromBase32hex(str, options) {
|
|
22
|
+
if (!options) return fromBase32common(str, true, 'both', 'uint8', null)
|
|
23
|
+
const { format = 'uint8', padding = 'both', ...rest } = options
|
|
24
|
+
return fromBase32common(str, true, padding, format, rest)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function fromBase32common(str, isBase32Hex, padding, format, rest) {
|
|
28
|
+
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
29
|
+
if (rest !== null) assertEmptyRest(rest)
|
|
30
|
+
|
|
31
|
+
if (padding === true) {
|
|
32
|
+
if (str.length % 8 !== 0) throw new SyntaxError(E_PADDING)
|
|
33
|
+
} else if (padding === false) {
|
|
34
|
+
if (str.endsWith('=')) throw new SyntaxError('Did not expect padding in base32 input')
|
|
35
|
+
} else if (padding !== 'both') {
|
|
36
|
+
throw new TypeError('Invalid padding option')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return typedView(js.fromBase32(str, isBase32Hex), format)
|
|
40
|
+
}
|
package/base58.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { typedView } from './array.js'
|
|
2
|
+
import { assertUint8 } from './assert.js'
|
|
3
|
+
import { nativeDecoder, nativeEncoder, isHermes } from './fallback/_utils.js'
|
|
4
|
+
import { encodeAscii, decodeAscii } from './fallback/latin1.js'
|
|
5
|
+
|
|
6
|
+
const alphabet58 = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
|
|
7
|
+
const alphabetXRP = [...'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz']
|
|
8
|
+
const codes58 = new Uint8Array(alphabet58.map((x) => x.charCodeAt(0)))
|
|
9
|
+
const codesXRP = new Uint8Array(alphabetXRP.map((x) => x.charCodeAt(0)))
|
|
10
|
+
|
|
11
|
+
const _0n = BigInt(0)
|
|
12
|
+
const _1n = BigInt(1)
|
|
13
|
+
const _8n = BigInt(8)
|
|
14
|
+
const _32n = BigInt(32)
|
|
15
|
+
const _58n = BigInt(58)
|
|
16
|
+
const _0xffffffffn = BigInt(0xff_ff_ff_ff)
|
|
17
|
+
|
|
18
|
+
let table // 15 * 82, diagonal, <1kb
|
|
19
|
+
const fromMaps = new Map()
|
|
20
|
+
|
|
21
|
+
const E_CHAR = 'Invalid character in base58 input'
|
|
22
|
+
|
|
23
|
+
const shouldUseBigIntFrom = isHermes // faster only on Hermes, numbers path beats it on normal engines
|
|
24
|
+
|
|
25
|
+
function toBase58core(arr, alphabet, codes) {
|
|
26
|
+
assertUint8(arr)
|
|
27
|
+
const length = arr.length
|
|
28
|
+
if (length === 0) return ''
|
|
29
|
+
|
|
30
|
+
const ZERO = alphabet[0]
|
|
31
|
+
let zeros = 0
|
|
32
|
+
while (zeros < length && arr[zeros] === 0) zeros++
|
|
33
|
+
|
|
34
|
+
if (length > 60) {
|
|
35
|
+
// Slow path. Can be optimized ~10%, but the main factor is /58n division anyway, so doesn't matter much
|
|
36
|
+
let x = _0n
|
|
37
|
+
for (let i = 0; i < arr.length; i++) x = (x << _8n) | BigInt(arr[i])
|
|
38
|
+
|
|
39
|
+
let out = ''
|
|
40
|
+
while (x) {
|
|
41
|
+
const d = x / _58n
|
|
42
|
+
out = alphabet[Number(x - _58n * d)] + out
|
|
43
|
+
x = d
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return ZERO.repeat(zeros) + out
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// We run fast mode operations only on short (<=60 bytes) inputs, via precomputation table
|
|
50
|
+
if (!table) {
|
|
51
|
+
table = []
|
|
52
|
+
let x = _1n
|
|
53
|
+
for (let i = 0; i < 15; i++) {
|
|
54
|
+
// Convert x to base 58 digits
|
|
55
|
+
const in58 = []
|
|
56
|
+
let y = x
|
|
57
|
+
while (y) {
|
|
58
|
+
const d = y / _58n
|
|
59
|
+
in58.push(Number(y - _58n * d))
|
|
60
|
+
y = d
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
table.push(new Uint8Array(in58))
|
|
64
|
+
x <<= _32n
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const res = []
|
|
69
|
+
{
|
|
70
|
+
let j = 0
|
|
71
|
+
// We group each 4 bytes into 32-bit chunks
|
|
72
|
+
// Not using u32arr to not deal with remainder + BE/LE differences
|
|
73
|
+
for (let i = length - 1; i >= 0; i -= 4) {
|
|
74
|
+
let c
|
|
75
|
+
if (i > 2) {
|
|
76
|
+
c = (arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16) | (arr[i - 3] << 24)) >>> 0
|
|
77
|
+
} else if (i > 1) {
|
|
78
|
+
c = arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16)
|
|
79
|
+
} else {
|
|
80
|
+
c = i === 1 ? arr[i] | (arr[i - 1] << 8) : arr[i]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const row = table[j++]
|
|
84
|
+
if (c === 0) continue
|
|
85
|
+
const olen = res.length
|
|
86
|
+
const nlen = row.length
|
|
87
|
+
let k = 0
|
|
88
|
+
for (; k < olen; k++) res[k] += c * row[k]
|
|
89
|
+
while (k < nlen) res.push(c * row[k++])
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// We can now do a single scan over regular numbers under MAX_SAFE_INTEGER
|
|
94
|
+
// Note: can't use int32 operations on them, as they are outside of 2**32 range
|
|
95
|
+
// This is faster though
|
|
96
|
+
{
|
|
97
|
+
let carry = 0
|
|
98
|
+
let i = 0
|
|
99
|
+
while (i < res.length) {
|
|
100
|
+
const c = res[i] + carry
|
|
101
|
+
carry = Math.floor(c / 58)
|
|
102
|
+
res[i++] = c - carry * 58
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
while (carry) {
|
|
106
|
+
const c = carry
|
|
107
|
+
carry = Math.floor(c / 58)
|
|
108
|
+
res.push(c - carry * 58)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (nativeDecoder) {
|
|
113
|
+
const oa = new Uint8Array(res.length)
|
|
114
|
+
let j = 0
|
|
115
|
+
for (let i = res.length - 1; i >= 0; i--) oa[j++] = codes[res[i]]
|
|
116
|
+
return ZERO.repeat(zeros) + decodeAscii(oa)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let out = ''
|
|
120
|
+
for (let i = res.length - 1; i >= 0; i--) out += alphabet[res[i]]
|
|
121
|
+
return ZERO.repeat(zeros) + out
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function fromBase58core(str, alphabet, codes, format = 'uint8') {
|
|
125
|
+
if (typeof str !== 'string') throw new TypeError('Input is not a string')
|
|
126
|
+
const length = str.length
|
|
127
|
+
if (length === 0) return typedView(new Uint8Array(), format)
|
|
128
|
+
|
|
129
|
+
const zeroC = codes[0]
|
|
130
|
+
let zeros = 0
|
|
131
|
+
while (zeros < length && str.charCodeAt(zeros) === zeroC) zeros++
|
|
132
|
+
|
|
133
|
+
let fromMap = fromMaps.get(alphabet)
|
|
134
|
+
if (!fromMap) {
|
|
135
|
+
fromMap = new Int8Array(256).fill(-1)
|
|
136
|
+
for (let i = 0; i < 58; i++) fromMap[alphabet[i].charCodeAt(0)] = i
|
|
137
|
+
fromMaps.set(alphabet, fromMap)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const size = zeros + (((length - zeros + 1) * 3) >> 2) // 3/4 rounded up, larger than ~0.73 coef to fit everything
|
|
141
|
+
const res = new Uint8Array(size)
|
|
142
|
+
let at = size // where is the first significant byte written
|
|
143
|
+
|
|
144
|
+
if (shouldUseBigIntFrom) {
|
|
145
|
+
let x = _0n
|
|
146
|
+
|
|
147
|
+
// nativeEncoder gives a benefit here
|
|
148
|
+
if (nativeEncoder) {
|
|
149
|
+
const codes = encodeAscii(str, E_CHAR)
|
|
150
|
+
for (let i = zeros; i < length; i++) {
|
|
151
|
+
const c = fromMap[codes[i]]
|
|
152
|
+
if (c < 0) throw new SyntaxError(E_CHAR)
|
|
153
|
+
x = x * _58n + BigInt(c)
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
for (let i = zeros; i < length; i++) {
|
|
157
|
+
const charCode = str.charCodeAt(i)
|
|
158
|
+
const c = fromMap[charCode]
|
|
159
|
+
if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
|
|
160
|
+
x = x * _58n + BigInt(c)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
while (x) {
|
|
165
|
+
let y = Number(x & _0xffffffffn)
|
|
166
|
+
x >>= 32n
|
|
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
|
+
y >>>= 8
|
|
175
|
+
if (!x && !y) break
|
|
176
|
+
res[--at] = y & 0xff
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
for (let i = zeros; i < length; i++) {
|
|
180
|
+
const charCode = str.charCodeAt(i)
|
|
181
|
+
let c = fromMap[charCode]
|
|
182
|
+
if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
|
|
183
|
+
|
|
184
|
+
let k = size - 1
|
|
185
|
+
for (;;) {
|
|
186
|
+
if (c === 0 && k < at) break
|
|
187
|
+
c += 58 * res[k]
|
|
188
|
+
res[k] = c & 0xff
|
|
189
|
+
c >>>= 8
|
|
190
|
+
k--
|
|
191
|
+
// unroll a bit
|
|
192
|
+
if (c === 0 && k < at) break
|
|
193
|
+
c += 58 * res[k]
|
|
194
|
+
res[k] = c & 0xff
|
|
195
|
+
c >>>= 8
|
|
196
|
+
k--
|
|
197
|
+
if (c === 0 && k < at) break
|
|
198
|
+
c += 58 * res[k]
|
|
199
|
+
res[k] = c & 0xff
|
|
200
|
+
c >>>= 8
|
|
201
|
+
k--
|
|
202
|
+
if (c === 0 && k < at) break
|
|
203
|
+
c += 58 * res[k]
|
|
204
|
+
res[k] = c & 0xff
|
|
205
|
+
c >>>= 8
|
|
206
|
+
k--
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
at = k + 1
|
|
210
|
+
if (c !== 0 || at < zeros) throw new Error('Unexpected') // unreachable
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return typedView(res.slice(at - zeros), format) // slice is faster for small sizes than subarray
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const toBase58 = (arr) => toBase58core(arr, alphabet58, codes58)
|
|
218
|
+
export const fromBase58 = (str, format) => fromBase58core(str, alphabet58, codes58, format)
|
|
219
|
+
export const toBase58xrp = (arr) => toBase58core(arr, alphabetXRP, codesXRP)
|
|
220
|
+
export const fromBase58xrp = (str, format) => fromBase58core(str, alphabetXRP, codesXRP, format)
|
package/base58check.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
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' // eslint-disable-line @exodus/import/no-deprecated
|
|
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 E_CHECKSUM = 'Invalid checksum'
|
|
10
|
+
|
|
11
|
+
// checksum length is 4, i.e. only the first 4 bytes of the hash are used
|
|
12
|
+
|
|
13
|
+
function encodeWithChecksum(arr, checksum) {
|
|
14
|
+
// arr type in already validated in input
|
|
15
|
+
const res = new Uint8Array(arr.length + 4)
|
|
16
|
+
res.set(arr, 0)
|
|
17
|
+
res.set(checksum.subarray(0, 4), arr.length)
|
|
18
|
+
return toBase58(res)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function decodeWithChecksum(str) {
|
|
22
|
+
const arr = fromBase58(str) // checks input
|
|
23
|
+
const payloadSize = arr.length - 4
|
|
24
|
+
if (payloadSize < 0) throw new Error(E_CHECKSUM)
|
|
25
|
+
return [arr.subarray(0, payloadSize), arr.subarray(payloadSize)]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertChecksum(c, r) {
|
|
29
|
+
if ((c[0] ^ r[0]) | (c[1] ^ r[1]) | (c[2] ^ r[2]) | (c[3] ^ r[3])) throw new Error(E_CHECKSUM)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const makeBase58check = (hashAlgo, hashAlgoSync) => {
|
|
33
|
+
const apis = {
|
|
34
|
+
async encode(arr) {
|
|
35
|
+
assertUint8(arr)
|
|
36
|
+
return encodeWithChecksum(arr, await hashAlgo(arr))
|
|
37
|
+
},
|
|
38
|
+
async decode(str, format = 'uint8') {
|
|
39
|
+
const [payload, checksum] = decodeWithChecksum(str)
|
|
40
|
+
assertChecksum(checksum, await hashAlgo(payload))
|
|
41
|
+
return typedView(payload, format)
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
if (!hashAlgoSync) return apis
|
|
45
|
+
return {
|
|
46
|
+
...apis,
|
|
47
|
+
encodeSync(arr) {
|
|
48
|
+
assertUint8(arr)
|
|
49
|
+
return encodeWithChecksum(arr, hashAlgoSync(arr))
|
|
50
|
+
},
|
|
51
|
+
decodeSync(str, format = 'uint8') {
|
|
52
|
+
const [payload, checksum] = decodeWithChecksum(str)
|
|
53
|
+
assertChecksum(checksum, hashAlgoSync(payload))
|
|
54
|
+
return typedView(payload, format)
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// eslint-disable-next-line @exodus/import/no-deprecated
|
|
60
|
+
const hash256sync = (x) => hashSync('sha256', hashSync('sha256', x, 'uint8'), 'uint8')
|
|
61
|
+
const hash256 = hash256sync // See note at the top
|
|
62
|
+
const {
|
|
63
|
+
encode: toBase58check,
|
|
64
|
+
decode: fromBase58check,
|
|
65
|
+
encodeSync: toBase58checkSync,
|
|
66
|
+
decodeSync: fromBase58checkSync,
|
|
67
|
+
} = makeBase58check(hash256, hash256sync)
|
|
68
|
+
|
|
69
|
+
export { toBase58check, fromBase58check, toBase58checkSync, fromBase58checkSync }
|