| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- import { typedView } from './array.js'
- import { assertU8, E_STRING } from './fallback/_utils.js'
- import { nativeDecoder, nativeEncoder, isHermes } from './fallback/platform.js'
- import { encodeAscii, decodeAscii } from './fallback/latin1.js'
- const alphabet58 = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
- const alphabetXRP = [...'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz']
- const codes58 = new Uint8Array(alphabet58.map((x) => x.charCodeAt(0)))
- const codesXRP = new Uint8Array(alphabetXRP.map((x) => x.charCodeAt(0)))
- const _0n = BigInt(0)
- const _1n = BigInt(1)
- const _8n = BigInt(8)
- const _32n = BigInt(32)
- const _58n = BigInt(58)
- const _0xffffffffn = BigInt(0xff_ff_ff_ff)
- let table // 15 * 82, diagonal, <1kb
- const fromMaps = new Map()
- const E_CHAR = 'Invalid character in base58 input'
- const shouldUseBigIntFrom = isHermes // faster only on Hermes, numbers path beats it on normal engines
- function toBase58core(arr, alphabet, codes) {
- assertU8(arr)
- const length = arr.length
- if (length === 0) return ''
- const ZERO = alphabet[0]
- let zeros = 0
- while (zeros < length && arr[zeros] === 0) zeros++
- if (length > 60) {
- // Slow path. Can be optimized ~10%, but the main factor is /58n division anyway, so doesn't matter much
- let x = _0n
- for (let i = 0; i < arr.length; i++) x = (x << _8n) | BigInt(arr[i])
- let out = ''
- while (x) {
- const d = x / _58n
- out = alphabet[Number(x - _58n * d)] + out
- x = d
- }
- return ZERO.repeat(zeros) + out
- }
- // We run fast mode operations only on short (<=60 bytes) inputs, via precomputation table
- if (!table) {
- table = []
- let x = _1n
- for (let i = 0; i < 15; i++) {
- // Convert x to base 58 digits
- const in58 = []
- let y = x
- while (y) {
- const d = y / _58n
- in58.push(Number(y - _58n * d))
- y = d
- }
- table.push(new Uint8Array(in58))
- x <<= _32n
- }
- }
- const res = []
- {
- let j = 0
- // We group each 4 bytes into 32-bit chunks
- // Not using u32arr to not deal with remainder + BE/LE differences
- for (let i = length - 1; i >= 0; i -= 4) {
- let c
- if (i > 2) {
- c = (arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16) | (arr[i - 3] << 24)) >>> 0
- } else if (i > 1) {
- c = arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16)
- } else {
- c = i === 1 ? arr[i] | (arr[i - 1] << 8) : arr[i]
- }
- const row = table[j++]
- if (c === 0) continue
- const olen = res.length
- const nlen = row.length
- let k = 0
- for (; k < olen; k++) res[k] += c * row[k]
- while (k < nlen) res.push(c * row[k++])
- }
- }
- // We can now do a single scan over regular numbers under MAX_SAFE_INTEGER
- // Note: can't use int32 operations on them, as they are outside of 2**32 range
- // This is faster though
- {
- let carry = 0
- let i = 0
- while (i < res.length) {
- const c = res[i] + carry
- carry = Math.floor(c / 58)
- res[i++] = c - carry * 58
- }
- while (carry) {
- const c = carry
- carry = Math.floor(c / 58)
- res.push(c - carry * 58)
- }
- }
- if (nativeDecoder) {
- const oa = new Uint8Array(res.length)
- let j = 0
- for (let i = res.length - 1; i >= 0; i--) oa[j++] = codes[res[i]]
- return ZERO.repeat(zeros) + decodeAscii(oa)
- }
- let out = ''
- for (let i = res.length - 1; i >= 0; i--) out += alphabet[res[i]]
- return ZERO.repeat(zeros) + out
- }
- function fromBase58core(str, alphabet, codes, format = 'uint8') {
- if (typeof str !== 'string') throw new TypeError(E_STRING)
- const length = str.length
- if (length === 0) return typedView(new Uint8Array(), format)
- const zeroC = codes[0]
- let zeros = 0
- while (zeros < length && str.charCodeAt(zeros) === zeroC) zeros++
- let fromMap = fromMaps.get(alphabet)
- if (!fromMap) {
- fromMap = new Int8Array(256).fill(-1)
- for (let i = 0; i < 58; i++) fromMap[alphabet[i].charCodeAt(0)] = i
- fromMaps.set(alphabet, fromMap)
- }
- const size = zeros + (((length - zeros + 1) * 3) >> 2) // 3/4 rounded up, larger than ~0.73 coef to fit everything
- const res = new Uint8Array(size)
- let at = size // where is the first significant byte written
- if (shouldUseBigIntFrom) {
- let x = _0n
- // nativeEncoder gives a benefit here
- if (nativeEncoder) {
- const codes = encodeAscii(str, E_CHAR)
- for (let i = zeros; i < length; i++) {
- const c = fromMap[codes[i]]
- if (c < 0) throw new SyntaxError(E_CHAR)
- x = x * _58n + BigInt(c)
- }
- } else {
- for (let i = zeros; i < length; i++) {
- const charCode = str.charCodeAt(i)
- const c = fromMap[charCode]
- if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
- x = x * _58n + BigInt(c)
- }
- }
- while (x) {
- let y = Number(x & _0xffffffffn)
- x >>= 32n
- res[--at] = y & 0xff
- y >>>= 8
- if (!x && !y) break
- res[--at] = y & 0xff
- y >>>= 8
- if (!x && !y) break
- res[--at] = y & 0xff
- y >>>= 8
- if (!x && !y) break
- res[--at] = y & 0xff
- }
- } else {
- for (let i = zeros; i < length; i++) {
- const charCode = str.charCodeAt(i)
- let c = fromMap[charCode]
- if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
- let k = size - 1
- for (;;) {
- if (c === 0 && k < at) break
- c += 58 * res[k]
- res[k] = c & 0xff
- c >>>= 8
- k--
- // unroll a bit
- if (c === 0 && k < at) break
- c += 58 * res[k]
- res[k] = c & 0xff
- c >>>= 8
- k--
- if (c === 0 && k < at) break
- c += 58 * res[k]
- res[k] = c & 0xff
- c >>>= 8
- k--
- if (c === 0 && k < at) break
- c += 58 * res[k]
- res[k] = c & 0xff
- c >>>= 8
- k--
- }
- at = k + 1
- if (c !== 0 || at < zeros) /* c8 ignore next */ throw new Error('Unexpected') // unreachable
- }
- }
- return typedView(res.slice(at - zeros), format) // slice is faster for small sizes than subarray
- }
- export const toBase58 = (arr) => toBase58core(arr, alphabet58, codes58)
- export const fromBase58 = (str, format) => fromBase58core(str, alphabet58, codes58, format)
- export const toBase58xrp = (arr) => toBase58core(arr, alphabetXRP, codesXRP)
- export const fromBase58xrp = (str, format) => fromBase58core(str, alphabetXRP, codesXRP, format)
|