Home Reference Source

packages/oo7-substrate/src/ss58.js

  1. const bs58 = require('bs58')
  2. const { blake2b } = require('blakejs')
  3. const { toLE, leToNumber } = require('./utils')
  4. const { AccountIndex, AccountId } = require('./types')
  5.  
  6. let defaultType = 42
  7. const KNOWN_TYPES = [0, 1, 42, 43, 68, 69]
  8.  
  9. function setNetworkDefault(type) {
  10. defaultType = type
  11. }
  12.  
  13. function ss58Encode(a, type = defaultType, checksumLength = null, length = null, accountId) {
  14. let payload
  15. if (KNOWN_TYPES.indexOf(type) === -1) {
  16. throw new Error('Unknown ss58 address type', type)
  17. }
  18. if (typeof a === 'number' || a instanceof AccountIndex) {
  19. let minLength = (a < (1 << 8) ? 1 : a < (1 << 16) ? 2 : a < (1 << 32) ? 4 : 8)
  20. length = length ? length : minLength
  21. if ([1, 2, 4, 8].indexOf(length) === -1) {
  22. throw new Error('Invalid length')
  23. }
  24. length = Math.max(minLength, length)
  25. if (checksumLength && typeof checksumLength !== 'number') {
  26. throw new Error('Invalid checksum length')
  27. }
  28. switch (length) {
  29. case 1: { checksumLength = 1; break; }
  30. case 2: { checksumLength = ([1, 2].indexOf(checksumLength) + 1) || 1; break; }
  31. case 4: { checksumLength = ([1, 2, 3, 4].indexOf(checksumLength) + 1) || 1; break; }
  32. case 8: { checksumLength = ([1, 2, 3, 4, 5, 6, 7, 8].indexOf(checksumLength) + 1) || 1; break; }
  33. }
  34. payload = toLE(a, length)
  35. } else if ((a instanceof AccountId || a instanceof Uint8Array) && a.length === 32) {
  36. checksumLength = 2
  37. payload = a
  38. accountId = a
  39. } else {
  40. throw new Error('Unknown item to encode as ss58. Passing back.', a)
  41. }
  42. let hash = blake2b((type & 1) ? accountId : new Uint8Array([type, ...payload]))
  43. let complete = new Uint8Array([type, ...payload, ...hash.slice(0, checksumLength)])
  44. return bs58.encode(Buffer.from(complete))
  45. }
  46.  
  47. /// `lookupIndex` must be synchronous. If you can do that, then throw, catch outside the
  48. /// invocation and then retry once you have the result to hand.
  49. function ss58Decode(ss58, lookupIndex) {
  50. let a
  51. try {
  52. a = bs58.decode(ss58)
  53. }
  54. catch (e) {
  55. return null
  56. }
  57.  
  58. let type = a[0]
  59. if (KNOWN_TYPES.indexOf(type) === -1) {
  60. return null
  61. }
  62.  
  63. if (a.length < 3) {
  64. return null
  65. //throw new Error('Invalid length of payload for address', a.length)
  66. }
  67. let length = a.length <= 3
  68. ? 1
  69. : a.length <= 5
  70. ? 2
  71. : a.length <= 9
  72. ? 4
  73. : a.length <= 17
  74. ? 8
  75. : 32
  76. let checksumLength = a.length - 1 - length
  77.  
  78. let payload = a.slice(1, 1 + length)
  79. let checksum = a.slice(1 + a.length)
  80.  
  81. let accountId
  82. if (length === 32) {
  83. accountId = payload
  84. }
  85.  
  86. let result = length < 32
  87. ? new AccountIndex(leToNumber(payload))
  88. : new AccountId(payload)
  89.  
  90. if (a[0] % 1 && !accountId && !lookupIndex) {
  91. return null
  92. }
  93. let hash = blake2b(a[0] % 1 ? (accountId || lookupIndex(result)) : a.slice(0, 1 + length))
  94.  
  95. for (var i = 0; i < checksumLength; ++i) {
  96. if (hash[i] !== a[1 + length + i]) {
  97. // invalid checksum
  98. return null
  99. }
  100. }
  101.  
  102. return result
  103. }
  104.  
  105. module.exports = { ss58Decode, ss58Encode, setNetworkDefault }