Home Reference Source

packages/oo7-substrate/src/secretStore.js

  1. const { Bond } = require('oo7')
  2. const nacl = require('tweetnacl');
  3. const { generateMnemonic, mnemonicToSeed } = require('bip39')
  4. const { ss58Encode } = require('./ss58')
  5. const { AccountId } = require('./types')
  6. const { bytesToHex, hexToBytes } = require('./utils')
  7.  
  8. let cache = {}
  9.  
  10. function seedFromPhrase(phrase) {
  11. if (!cache[phrase]) {
  12. cache[phrase] = phrase.match(/^0x[0-9a-fA-F]{64}$/)
  13. ? hexToBytes(phrase)
  14. : new Uint8Array(mnemonicToSeed(phrase).slice(0, 32))
  15. }
  16. return cache[phrase]
  17. }
  18.  
  19. class SecretStore extends Bond {
  20. constructor (storage) {
  21. super()
  22. this._storage = storage || typeof localStorage === 'undefined' ? {} : localStorage
  23. this._keys = []
  24. this._load()
  25. }
  26.  
  27. submit (phrase, name) {
  28. this._keys.push({phrase, name})
  29. this._sync()
  30. return this.accountFromPhrase(phrase)
  31. }
  32.  
  33. accountFromPhrase (phrase) {
  34. return new AccountId(nacl.sign.keyPair.fromSeed(seedFromPhrase(phrase)).publicKey)
  35. }
  36.  
  37. accounts () {
  38. return this._keys.map(k => k.account)
  39. }
  40.  
  41. find (identifier) {
  42. if (this._keys.indexOf(identifier) !== -1) {
  43. return identifier
  44. }
  45. if (identifier instanceof Uint8Array && identifier.length == 32 || identifier instanceof AccountId) {
  46. identifier = ss58Encode(identifier)
  47. }
  48. return this._byAddress[identifier] ? this._byAddress[identifier] : this._byName[identifier]
  49. }
  50.  
  51. sign (from, data) {
  52. let item = this.find(from)
  53. if (item) {
  54. console.info(`Signing data from ${item.name}`, bytesToHex(data))
  55. let sig = nacl.sign.detached(data, item.key.secretKey)
  56. console.info(`Signature is ${bytesToHex(sig)}`)
  57. if (!nacl.sign.detached.verify(data, sig, item.key.publicKey)) {
  58. console.warn(`Signature is INVALID!`)
  59. return null
  60. }
  61. return sig
  62. }
  63. return null
  64. }
  65.  
  66. forget (identifier) {
  67. let item = this.find(identifier)
  68. if (item) {
  69. console.info(`Forgetting key ${item.name} (${item.address}, ${item.phrase})`)
  70. this._keys = this._keys.filter(i => i !== item)
  71. this._sync()
  72. }
  73. }
  74.  
  75. _load () {
  76. if (this._storage.secretStore) {
  77. this._keys = JSON.parse(this._storage.secretStore).map(({seed, phrase, name}) => ({ phrase, name, seed: hexToBytes(seed) }))
  78. } else if (this._storage.secretStore2) {
  79. this._keys = JSON.parse(this._storage.secretStore2).map(({seed, name}) => ({ phrase: seed, name }))
  80. } else {
  81. this._keys = [{
  82. name: 'Default',
  83. phrase: generateMnemonic()
  84. }]
  85. }
  86. this._sync()
  87. }
  88.  
  89. _sync () {
  90. let byAddress = {}
  91. let byName = {}
  92. this._keys = this._keys.map(({seed, phrase, name, key}) => {
  93. seed = seed || seedFromPhrase(phrase)
  94. key = key || nacl.sign.keyPair.fromSeed(seed)
  95. let account = new AccountId(key.publicKey)
  96. let address = ss58Encode(account)
  97. let item = {seed, phrase, name, key, account, address}
  98. byAddress[address] = item
  99. byName[name] = item
  100. return item
  101. })
  102. this._byAddress = byAddress
  103. this._byName = byName
  104. this._storage.secretStore = JSON.stringify(this._keys.map(k => ({seed: bytesToHex(k.seed), phrase: k.phrase, name: k.name})))
  105. this.trigger({keys: this._keys, byAddress: this._byAddress, byName: this._byName})
  106. }
  107. }
  108.  
  109. let s_secretStore = null;
  110.  
  111. function secretStore(storage) {
  112. if (s_secretStore === null) {
  113. s_secretStore = new SecretStore(storage);
  114. }
  115. return s_secretStore;
  116. }
  117.  
  118. module.exports = { secretStore, SecretStore };