UNPKG

six-caver-js

Version:

caver-js is a JavaScript API library that allows developers to interact with a Klaytn node

341 lines (299 loc) 15.1 kB
/* Copyright 2020 The caver-js Authors This file is part of the caver-js library. The caver-js library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The caver-js library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the caver-js. If not, see <http://www.gnu.org/licenses/>. */ const _ = require('lodash') const AccountLib = require('eth-lib/lib/account') const utils = require('../../../caver-utils/src') const PrivateKey = require('./privateKey') const { KEY_ROLE, isMultipleKeysFormat, isRoleBasedKeysFormat } = require('./keyringHelper') const { decryptKey } = require('./keyringHelper') const SingleKeyring = require('./singleKeyring') const MultipleKeyring = require('./multipleKeyring') const RoleBasedKeyring = require('./roleBasedKeyring') const SignatureData = require('./signatureData') /** * representing a KeyringFactory which supports create functions for Keyring(SingleKeyring/MultipleKeyring/RoleBasedKeyring) * @class */ class KeyringFactory { /** * generates a keyring instance * * `caver.wallet.keyring.generate()` * * @param {string} [entropy] A random string to increase entropy. * @return {SingleKeyring} */ static generate(entropy) { const random = AccountLib.create(entropy || utils.randomHex(32)) return KeyringFactory.createWithSingleKey(random.address, random.privateKey) } /** * generates a single private key string * * `caver.wallet.keyring.generateSingleKey()` * * @param {string} [entropy] A random string to increase entropy. * @return {String} */ static generateSingleKey(entropy) { return AccountLib.create(entropy || utils.randomHex(32)).privateKey } /** * generates an array of private key strings * * `caver.wallet.keyring.generateMultipleKeys()` * * @param {number} num A length of keys. * @param {string} [entropy] A random string to increase entropy. * @return {Array.<String>} */ static generateMultipleKeys(num, entropy) { if (num === undefined || !_.isNumber(num) || _.isString(num)) { throw new Error(`To generate random multiple private keys, the number of keys should be defined.`) } const randomKeys = [] for (let i = 0; i < num; i++) { randomKeys.push(AccountLib.create(entropy || utils.randomHex(32)).privateKey) } return randomKeys } /** * generates an array in which keys to be used for each role are defined as an array. * * `caver.wallet.keyring.generateRoleBasedKeys()` * * @param {Array.<number>} numArr An array containing the number of keys for each role. * @param {string} [entropy] A random string to increase entropy. * @return {Array.<Array.<String>>} */ static generateRoleBasedKeys(numArr, entropy) { if (numArr === undefined || !_.isArray(numArr) || _.isString(numArr)) { throw new Error( `To generate random role-based private keys, an array containing the number of keys for each role should be defined.` ) } if (numArr.length > KEY_ROLE.roleLast) { throw new Error(`Unsupported role. The length of array should be less than ${KEY_ROLE.roleLast}.`) } const randomKeys = [[], [], []] for (let i = 0; i < numArr.length; i++) { for (let j = 0; j < numArr[i]; j++) { randomKeys[i].push(AccountLib.create(entropy || utils.randomHex(32)).privateKey) } } return randomKeys } /** * creates a keyring instance with parameters * * `caver.wallet.keyring.create('0x${address in hex}', '0x{private key}')` * `caver.wallet.keyring.create('0x${address in hex}', ['0x{private key}', '0x{private key}'])` * `caver.wallet.keyring.create('0x${address in hex}', [['0x{private key}', '0x{private key}'], ['0x{private key}'], ['0x{private key}', '0x{private key}']])` * * @param {string} address An address of keyring. * @param {string|Array.<string>|Array.<Array.<string>>} key Private key(s) to use in keyring. * @return {SingleKeyring|MultipleKeyring|RoleBasedKeyring} */ static create(address, key) { if (_.isString(key)) return KeyringFactory.createWithSingleKey(address, key) if (isMultipleKeysFormat(key)) return KeyringFactory.createWithMultipleKey(address, key) if (isRoleBasedKeysFormat(key)) return KeyringFactory.createWithRoleBasedKey(address, key) throw new Error(`Unsupported key type: ${typeof key}`) } /** * creates a keyring instance from a private key string. KlaytnWalletKey format also can be handled. * * @param {string} privateKey The key parameter can be either normal private key or KlaytnWalletKey format. * @return {SingleKeyring} */ static createFromPrivateKey(privateKey) { if (!_.isString(privateKey)) throw new Error(`Invalid format of parameter. 'privateKey' should be in format of string`) if (utils.isKlaytnWalletKey(privateKey)) return KeyringFactory.createFromKlaytnWalletKey(privateKey) const acct = AccountLib.fromPrivate(utils.addHexPrefix(privateKey)) return KeyringFactory.createWithSingleKey(acct.address, acct.privateKey) } /** * creates a keyring instance from a KlaytnWalletKey string. * * @param {string} klaytnWalletKey A key string in KlaytnWalletKey format. * @return {SingleKeyring} */ static createFromKlaytnWalletKey(klaytnWalletKey) { if (!_.isString(klaytnWalletKey)) throw new Error(`Invalid format of parameter. 'klaytnWalletKey' should be in format of string`) if (!utils.isKlaytnWalletKey(klaytnWalletKey)) { throw new Error(`Invalid KlaytnWalletKey: ${klaytnWalletKey}`) } const parsed = utils.parsePrivateKey(klaytnWalletKey) return KeyringFactory.createWithSingleKey(parsed.address, parsed.privateKey) } /** * creates a keyring instance from an address and a private key string. * * @param {string} address An address of keyring. * @param {string} key A private key string. * @return {SingleKeyring} */ static createWithSingleKey(address, key) { if (!_.isString(key)) throw new Error(`Invalid format of parameter. Use 'fromMultipleKey' or 'fromRoleBasedKey' for two or more keys.`) if (utils.isKlaytnWalletKey(key)) throw new Error(`Invalid format of parameter. Use 'fromKlaytnWalletKey' to create Keyring from KlaytnWalletKey.`) return new SingleKeyring(address, key) } /** * creates a keyring instance from an address and multiple private key strings. * * @param {string} address An address of keyring. * @param {Array.<string>} keyArray An array of private key strings. * @return {MultipleKeyring} */ static createWithMultipleKey(address, keyArray) { if (!isMultipleKeysFormat(keyArray)) throw new Error(`Invalid format of parameter. 'keyArray' should be an array of private key strings.`) return new MultipleKeyring(address, keyArray) } /** * creates a keyring instance from an address and an array in which keys to be used for each role are defined as an array. * * @param {string} address An address of keyring. * @param {Array.<Array.<string>>} roledBasedKeyArray A two-dimensional array containing arrays of private key strings for each role. * @return {RoleBasedKeyring} */ static createWithRoleBasedKey(address, roledBasedKeyArray) { if (!isRoleBasedKeysFormat(roledBasedKeyArray)) throw new Error( `Invalid format of parameter. 'roledBasedKeyArray' should be in the form of an array defined as an array for the keys to be used for each role.` ) return new RoleBasedKeyring(address, roledBasedKeyArray) } /** * decrypts a keystore v3 or v4 JSON and returns keyring instance. * * @param {object} keystore The encrypted keystore to decrypt. * @param {string} password The password to use for decryption. * @return {SingleKeyring|MultipleKeyring|RoleBasedKeyring} */ static decrypt(keystore, password) { // To deep copy an object, using JSON.parse and JSON.stringify (object -> string -> object) const json = _.isObject(keystore) ? _.cloneDeep(keystore) : JSON.parse(keystore) if (json.version !== 3 && json.version !== 4) console.warn('This is not a V3 or V4 wallet.') if (json.version === 3 && !json.crypto) { throw new Error("Invalid keystore V3 format: 'crypto' is not defined.") } else if (json.version === 4 && !json.keyring) { throw new Error("Invalid keystore V4 format: 'keyring' is not defined.") } if (json.crypto) { if (json.keyring) throw new Error("Invalid key store format: 'crypto' and 'keyring' cannot be defined together.") json.keyring = [json.crypto] delete json.crypto } // AccountKeyRoleBased format if (_.isArray(json.keyring[0])) { const keys = [] const transactionKey = decryptKey(json.keyring[KEY_ROLE.roleTransactionKey], password) transactionKey ? keys.push(transactionKey) : keys.push([]) const updateKey = decryptKey(json.keyring[KEY_ROLE.roleAccountUpdateKey], password) updateKey ? keys.push(updateKey) : keys.push([]) const feePayerKey = decryptKey(json.keyring[KEY_ROLE.roleFeePayerKey], password) feePayerKey ? keys.push(feePayerKey) : keys.push([]) return KeyringFactory.createWithRoleBasedKey(json.address, keys) } let decrypted = decryptKey(json.keyring, password) decrypted = _.isArray(decrypted) ? decrypted : [decrypted] if (decrypted.length === 1) return KeyringFactory.createWithSingleKey(json.address, decrypted[0]) return KeyringFactory.createWithMultipleKey(json.address, decrypted) } // /** // * encrypts a keyring and returns a keystore v4 object. // * // * @param {string|Array.<string>|Array.<string>|Keyring} key The key parameter can be an instance of Keyring, a normal private key(KlaytnWalletKey format also supported), // * an array of private key strings, or a two-dimensional array containing arrays of private key strings for each role, // * @param {string} password The password to be used for encryption. The encrypted key store can be decrypted with this password. // * @param {object} options The options to use when encrypt a keyring. See `keyring.encrypt` for more detail about options. // * @return {object} // */ // static encrypt(key, password, options = {}) { // let keyring // if (_.isArray(key)) { // if (options.address === undefined) // throw new Error(`The address must be defined inside the options object to encrypt multiple keys.`) // if (isRoleBasedKeysFormat(key)) { // keyring = KeyringFactory.createWithRoleBasedKey(options.address, key) // } else if (isMultipleKeysFormat(key)) { // keyring = KeyringFactory.createWithMultipleKey(options.address, key) // } else { // throw new Error(`Invalid key format.`) // } // } else if (key instanceof AbstractKeyring) { // keyring = key // } else if (_.isString(key)) { // if (options.address) { // if (utils.isKlaytnWalletKey(key)) { // keyring = KeyringFactory.createFromKlaytnWalletKey(key) // if (keyring.address.toLowerCase() !== options.address.toLowerCase()) { // throw new Error( // `The address defined in options(${options.address}) does not match the address of KlaytnWalletKey(${keyring.address}) entered as a parameter.` // ) // } // } else { // keyring = KeyringFactory.createWithSingleKey(options.address, key) // } // } else { // keyring = KeyringFactory.createFromPrivateKey(key) // } // } else { // throw new Error(`Invalid key format.`) // } // return keyring.encrypt(password, options) // } // /** // * encrypts a keyring and returns a keystore v3 object. // * // * @param {string|Keyring} key The key parameter can be a normal private key(KlaytnWalletKey format also supported) or an instance of Keyring. // * @param {string} password The password to be used for keyring encryption. The encrypted key store can be decrypted with this password. // * @param {object} options The options to use when encrypt a keyring. See `keyring.encrypt` for more detail about options. // * @return {object} // */ // static encryptV3(key, password, options = {}) { // if (!_.isString(key) && !(key instanceof Keyring)) { // throw new Error(`Invalid parameter. key should be a private key string, KlaytnWalletKey or instance of Keyring`) // } // let keyring // if (key instanceof Keyring) { // keyring = key // } else if (options.address) { // if (utils.isKlaytnWalletKey(key)) { // keyring = KeyringFactory.createFromKlaytnWalletKey(key) // if (keyring.address.toLowerCase() !== options.address.toLowerCase()) { // throw new Error( // `The address defined in options(${options.address}) does not match the address of KlaytnWalletKey(${keyring.address}) entered as a parameter.` // ) // } // } else { // keyring = KeyringFactory.createWithSingleKey(options.address, key) // } // } else { // keyring = KeyringFactory.createFromPrivateKey(key) // } // return keyring.encryptV3(password, options) // } } KeyringFactory.privateKey = PrivateKey KeyringFactory.singleKeyring = SingleKeyring KeyringFactory.multipleKeyring = MultipleKeyring KeyringFactory.roleBasedKeyring = RoleBasedKeyring KeyringFactory.role = KEY_ROLE KeyringFactory.signatureData = SignatureData module.exports = KeyringFactory