identities.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. * This file is part of the storage node for the Joystream project.
  3. * Copyright (C) 2019 Joystream Contributors
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. 'use strict';
  19. const path = require('path');
  20. const fs = require('fs');
  21. // const readline = require('readline');
  22. const debug = require('debug')('joystream:runtime:identities');
  23. const { Keyring } = require('@polkadot/keyring');
  24. // const { Null } = require('@polkadot/types/primitive');
  25. const util_crypto = require('@polkadot/util-crypto');
  26. // const { _ } = require('lodash');
  27. /*
  28. * Add identity management to the substrate API.
  29. *
  30. * This loosely groups: accounts, key management, and membership.
  31. */
  32. class IdentitiesApi
  33. {
  34. static async create(base, {account_file, passphrase, canPromptForPassphrase})
  35. {
  36. const ret = new IdentitiesApi();
  37. ret.base = base;
  38. await ret.init(account_file, passphrase, canPromptForPassphrase);
  39. return ret;
  40. }
  41. async init(account_file, passphrase, canPromptForPassphrase)
  42. {
  43. debug('Init');
  44. // Creatre keyring
  45. this.keyring = new Keyring();
  46. this.canPromptForPassphrase = canPromptForPassphrase || false;
  47. // Load account file, if possible.
  48. try {
  49. this.key = await this.loadUnlock(account_file, passphrase);
  50. } catch (err) {
  51. debug('Error loading account file:', err.message);
  52. }
  53. }
  54. /*
  55. * Load a key file and unlock it if necessary.
  56. */
  57. async loadUnlock(account_file, passphrase)
  58. {
  59. const fullname = path.resolve(account_file);
  60. debug('Initializing key from', fullname);
  61. const key = this.keyring.addFromJson(require(fullname));
  62. await this.tryUnlock(key, passphrase);
  63. debug('Successfully initialized with address', key.address);
  64. return key;
  65. }
  66. /*
  67. * Try to unlock a key if it isn't already unlocked.
  68. * passphrase should be supplied as argument.
  69. */
  70. async tryUnlock(key, passphrase)
  71. {
  72. if (!key.isLocked) {
  73. debug('Key is not locked, not attempting to unlock')
  74. return;
  75. }
  76. // First try with an empty passphrase - for convenience
  77. try {
  78. key.decodePkcs8('');
  79. if (passphrase) {
  80. debug('Key was not encrypted, supplied passphrase was ignored');
  81. }
  82. return;
  83. } catch (err) {
  84. // pass
  85. }
  86. // Then with supplied passphrase
  87. try {
  88. debug('Decrypting with supplied passphrase');
  89. key.decodePkcs8(passphrase);
  90. return;
  91. } catch (err) {
  92. // pass
  93. }
  94. // If that didn't work, ask for a passphrase if appropriate
  95. if (this.canPromptForPassphrase) {
  96. passphrase = await this.askForPassphrase(key.address);
  97. key.decodePkcs8(passphrase);
  98. return
  99. }
  100. throw new Error('invalid passphrase supplied');
  101. }
  102. /*
  103. * Ask for a passphrase
  104. */
  105. askForPassphrase(address)
  106. {
  107. // Query for passphrase
  108. const prompt = require('password-prompt');
  109. return prompt(`Enter passphrase for ${address}: `, { required: false });
  110. }
  111. /*
  112. * Return true if the account is a member
  113. */
  114. async isMember(accountId)
  115. {
  116. const memberIds = await this.memberIdsOf(accountId); // return array of member ids
  117. return memberIds.length > 0 // true if at least one member id exists for the acccount
  118. }
  119. /*
  120. * Return true if the account is an actor/role account
  121. */
  122. async isActor(accountId)
  123. {
  124. const decoded = this.keyring.decodeAddress(accountId);
  125. const actor = await this.base.api.query.actors.actorByAccountId(decoded)
  126. return actor.isSome
  127. }
  128. /*
  129. * Return the member IDs of an account
  130. */
  131. async memberIdsOf(accountId)
  132. {
  133. const decoded = this.keyring.decodeAddress(accountId);
  134. return await this.base.api.query.members.memberIdsByRootAccountId(decoded);
  135. }
  136. /*
  137. * Return the first member ID of an account, or undefined if not a member.
  138. */
  139. async firstMemberIdOf(accountId)
  140. {
  141. const decoded = this.keyring.decodeAddress(accountId);
  142. let ids = await this.base.api.query.members.memberIdsByRootAccountId(decoded);
  143. return ids[0]
  144. }
  145. /*
  146. * Create a new key for the given role *name*. If no name is given,
  147. * default to 'storage'.
  148. */
  149. async createRoleKey(accountId, role)
  150. {
  151. role = role || 'storage';
  152. // Generate new key pair
  153. const keyPair = util_crypto.naclKeypairFromRandom();
  154. // Encode to an address.
  155. const addr = this.keyring.encodeAddress(keyPair.publicKey);
  156. debug('Generated new key pair with address', addr);
  157. // Add to key wring. We set the meta to identify the account as
  158. // a role key.
  159. const meta = {
  160. name: `${role} role account for ${accountId}`,
  161. };
  162. const createPair = require('@polkadot/keyring/pair').default;
  163. const pair = createPair('ed25519', keyPair, meta);
  164. this.keyring.addPair(pair);
  165. return pair;
  166. }
  167. /*
  168. * Export a key pair to JSON. Will ask for a passphrase.
  169. */
  170. async exportKeyPair(accountId)
  171. {
  172. const passphrase = await this.askForPassphrase(accountId);
  173. // Produce JSON output
  174. return this.keyring.toJson(accountId, passphrase);
  175. }
  176. /*
  177. * Export a key pair and write it to a JSON file with the account ID as the
  178. * name.
  179. */
  180. async writeKeyPairExport(accountId, prefix)
  181. {
  182. // Generate JSON
  183. const data = await this.exportKeyPair(accountId);
  184. // Write JSON
  185. var filename = `${data.address}.json`;
  186. if (prefix) {
  187. const path = require('path');
  188. filename = path.resolve(prefix, filename);
  189. }
  190. fs.writeFileSync(filename, JSON.stringify(data), {
  191. encoding: 'utf8',
  192. mode: 0o600,
  193. });
  194. return filename;
  195. }
  196. }
  197. module.exports = {
  198. IdentitiesApi: IdentitiesApi,
  199. }