base58.js 6.3 KB

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