base58.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import { assertU8, fromUint8, E_STRING } from './fallback/_utils.js'
  2. import { nativeDecoder, nativeEncoder, isHermes } from './fallback/platform.js'
  3. import { encodeAscii, decodeAscii } from './fallback/latin1.js'
  4. const alphabet58 = [...'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz']
  5. const alphabetXRP = [...'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz']
  6. const codes58 = new Uint8Array(alphabet58.map((x) => x.charCodeAt(0)))
  7. const codesXRP = new Uint8Array(alphabetXRP.map((x) => x.charCodeAt(0)))
  8. const _0n = BigInt(0)
  9. const _1n = BigInt(1)
  10. const _8n = BigInt(8)
  11. const _32n = BigInt(32)
  12. const _58n = BigInt(58)
  13. const _0xffffffffn = BigInt(0xff_ff_ff_ff)
  14. let table // 15 * 82, diagonal, <1kb
  15. const fromMaps = new Map()
  16. const E_CHAR = 'Invalid character in base58 input'
  17. const shouldUseBigIntFrom = isHermes // faster only on Hermes, numbers path beats it on normal engines
  18. function toBase58core(arr, alphabet, codes) {
  19. assertU8(arr)
  20. const length = arr.length
  21. if (length === 0) return ''
  22. const ZERO = alphabet[0]
  23. let zeros = 0
  24. while (zeros < length && arr[zeros] === 0) zeros++
  25. if (length > 60) {
  26. // Slow path. Can be optimized ~10%, but the main factor is /58n division anyway, so doesn't matter much
  27. let x = _0n
  28. for (let i = 0; i < arr.length; i++) x = (x << _8n) | BigInt(arr[i])
  29. let out = ''
  30. while (x) {
  31. const d = x / _58n
  32. out = alphabet[Number(x - _58n * d)] + out
  33. x = d
  34. }
  35. return ZERO.repeat(zeros) + out
  36. }
  37. // We run fast mode operations only on short (<=60 bytes) inputs, via precomputation table
  38. if (!table) {
  39. table = []
  40. let x = _1n
  41. for (let i = 0; i < 15; i++) {
  42. // Convert x to base 58 digits
  43. const in58 = []
  44. let y = x
  45. while (y) {
  46. const d = y / _58n
  47. in58.push(Number(y - _58n * d))
  48. y = d
  49. }
  50. table.push(new Uint8Array(in58))
  51. x <<= _32n
  52. }
  53. }
  54. const res = []
  55. {
  56. let j = 0
  57. // We group each 4 bytes into 32-bit chunks
  58. // Not using u32arr to not deal with remainder + BE/LE differences
  59. for (let i = length - 1; i >= 0; i -= 4) {
  60. let c
  61. if (i > 2) {
  62. c = (arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16) | (arr[i - 3] << 24)) >>> 0
  63. } else if (i > 1) {
  64. c = arr[i] | (arr[i - 1] << 8) | (arr[i - 2] << 16)
  65. } else {
  66. c = i === 1 ? arr[i] | (arr[i - 1] << 8) : arr[i]
  67. }
  68. const row = table[j++]
  69. if (c === 0) continue
  70. const olen = res.length
  71. const nlen = row.length
  72. let k = 0
  73. for (; k < olen; k++) res[k] += c * row[k]
  74. while (k < nlen) res.push(c * row[k++])
  75. }
  76. }
  77. // We can now do a single scan over regular numbers under MAX_SAFE_INTEGER
  78. // Note: can't use int32 operations on them, as they are outside of 2**32 range
  79. // This is faster though
  80. {
  81. let carry = 0
  82. let i = 0
  83. while (i < res.length) {
  84. const c = res[i] + carry
  85. carry = Math.floor(c / 58)
  86. res[i++] = c - carry * 58
  87. }
  88. while (carry) {
  89. const c = carry
  90. carry = Math.floor(c / 58)
  91. res.push(c - carry * 58)
  92. }
  93. }
  94. if (nativeDecoder) {
  95. const oa = new Uint8Array(res.length)
  96. let j = 0
  97. for (let i = res.length - 1; i >= 0; i--) oa[j++] = codes[res[i]]
  98. return ZERO.repeat(zeros) + decodeAscii(oa)
  99. }
  100. let out = ''
  101. for (let i = res.length - 1; i >= 0; i--) out += alphabet[res[i]]
  102. return ZERO.repeat(zeros) + out
  103. }
  104. function fromBase58core(str, alphabet, codes, format = 'uint8') {
  105. if (typeof str !== 'string') throw new TypeError(E_STRING)
  106. const length = str.length
  107. if (length === 0) return fromUint8(new Uint8Array(), format)
  108. const zeroC = codes[0]
  109. let zeros = 0
  110. while (zeros < length && str.charCodeAt(zeros) === zeroC) zeros++
  111. let fromMap = fromMaps.get(alphabet)
  112. if (!fromMap) {
  113. fromMap = new Int8Array(256).fill(-1)
  114. for (let i = 0; i < 58; i++) fromMap[alphabet[i].charCodeAt(0)] = i
  115. fromMaps.set(alphabet, fromMap)
  116. }
  117. const size = zeros + (((length - zeros + 1) * 3) >> 2) // 3/4 rounded up, larger than ~0.73 coef to fit everything
  118. const res = new Uint8Array(size)
  119. let at = size // where is the first significant byte written
  120. if (shouldUseBigIntFrom) {
  121. let x = _0n
  122. // nativeEncoder gives a benefit here
  123. if (nativeEncoder) {
  124. const codes = encodeAscii(str, E_CHAR)
  125. for (let i = zeros; i < length; i++) {
  126. const c = fromMap[codes[i]]
  127. if (c < 0) throw new SyntaxError(E_CHAR)
  128. x = x * _58n + BigInt(c)
  129. }
  130. } else {
  131. for (let i = zeros; i < length; i++) {
  132. const charCode = str.charCodeAt(i)
  133. const c = fromMap[charCode]
  134. if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
  135. x = x * _58n + BigInt(c)
  136. }
  137. }
  138. while (x) {
  139. let y = Number(x & _0xffffffffn)
  140. x >>= _32n
  141. res[--at] = y & 0xff
  142. y >>>= 8
  143. if (!x && !y) break
  144. res[--at] = y & 0xff
  145. y >>>= 8
  146. if (!x && !y) break
  147. res[--at] = y & 0xff
  148. y >>>= 8
  149. if (!x && !y) break
  150. res[--at] = y & 0xff
  151. }
  152. } else {
  153. for (let i = zeros; i < length; i++) {
  154. const charCode = str.charCodeAt(i)
  155. let c = fromMap[charCode]
  156. if (charCode > 255 || c < 0) throw new SyntaxError(E_CHAR)
  157. let k = size - 1
  158. for (;;) {
  159. if (c === 0 && k < at) break
  160. c += 58 * res[k]
  161. res[k] = c & 0xff
  162. c >>>= 8
  163. k--
  164. // unroll a bit
  165. if (c === 0 && k < at) break
  166. c += 58 * res[k]
  167. res[k] = c & 0xff
  168. c >>>= 8
  169. k--
  170. if (c === 0 && k < at) break
  171. c += 58 * res[k]
  172. res[k] = c & 0xff
  173. c >>>= 8
  174. k--
  175. if (c === 0 && k < at) break
  176. c += 58 * res[k]
  177. res[k] = c & 0xff
  178. c >>>= 8
  179. k--
  180. }
  181. at = k + 1
  182. if (c !== 0 || at < zeros) /* c8 ignore next */ throw new Error('Unexpected') // unreachable
  183. }
  184. }
  185. return fromUint8(res.slice(at - zeros), format)
  186. }
  187. export const toBase58 = (arr) => toBase58core(arr, alphabet58, codes58)
  188. export const fromBase58 = (str, format) => fromBase58core(str, alphabet58, codes58, format)
  189. export const toBase58xrp = (arr) => toBase58core(arr, alphabetXRP, codesXRP)
  190. export const fromBase58xrp = (str, format) => fromBase58core(str, alphabetXRP, codesXRP, format)