UNPKG

caver-js

Version:

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

465 lines (428 loc) 21.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({@link SingleKeyring}/{@link MultipleKeyring}/{@link RoleBasedKeyring}). * @class * @hideconstructor */ class KeyringFactory { /** * Generates a {@link SingleKeyring} instance with a randomly generated private key. * * @example * const keyring = caver.wallet.keyring.generate() * * @param {string} [entropy] A random string to increase entropy. * @return {SingleKeyring} A randomly generated single keyring instance is returned. */ static generate(entropy) { const random = AccountLib.create(entropy || utils.randomHex(32)) return KeyringFactory.createWithSingleKey(random.address, random.privateKey) } /** * generates a single private key string * * @example * const privateKey = caver.wallet.keyring.generateSingleKey() * * @param {string} [entropy] A random string to increase entropy. * @return {string} The private key string is returned. */ static generateSingleKey(entropy) { return AccountLib.create(entropy || utils.randomHex(32)).privateKey } /** * Generates an array of private key strings. * * @example * const privateKeys = caver.wallet.keyring.generateMultipleKeys() * * @param {number} num The number of private key strings. * @param {string} [entropy] A random string to increase entropy. * @return {Array.<string>} An array that includes private key strings is returned. */ 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 a 2D array of which each array element contains keys defined for each {@link https://docs.klaytn.com/klaytn/design/accounts#roles|role}. * * @example * const privateKeysByRoles = caver.wallet.keyring.generateRoleBasedKeys([2, 1, 3]) * * @param {Array.<number>} numArr An array containing the number of keys for each {@link https://docs.klaytn.com/klaytn/design/accounts#roles|role}. * @param {string} [entropy] A random string to increase entropy. * @return {Array.<Array.<string>>} A 2D array of which each array element contains keys defined for each role is returned. */ 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. * * If key is a private key string, a {@link SingleKeyring} instance that uses a single private key is created. * If key is an array containing private key strings, a {@link MultipleKeyring} instance that use multiple private keys is created. * If key is a 2D array of which each element contains the private key(s) to be used for each role, a {@link RoleBasedKeyring} instance is created. * * @example * const singleKeyring = caver.wallet.keyring.create('0x${address in hex}', '0x{private key}') * const multipleKeyring = caver.wallet.keyring.create('0x${address in hex}', ['0x{private key}', '0x{private key}']) * const roleBasedKeyring = 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 The private key string, an array of private keys, or a 2D array of which each element contains key(s) to be used for each {@link https://docs.klaytn.com/klaytn/design/accounts#roles|role}. * @return {KeyringContainer.Keyring} The keyring instance is returned. Depending on the key parameter, it can be {@link SingleKeyring}, {@link MultipleKeyring} or {@link 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 SingleKeyring instance from a private key string or a {@link https://docs.klaytn.com/klaytn/design/accounts#klaytn-wallet-key-format|KlaytnWalletKey}. * * @example * const keyring = caver.wallet.keyring.createFromPrivateKey('0x{private key}') * * @param {string} privateKey This parameter can be either a private key or KlaytnWalletKey. * @return {SingleKeyring} The SingleKeyring instance is returned. */ 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 SingleKeyring instance from a {@link https://docs.klaytn.com/klaytn/design/accounts#klaytn-wallet-key-format|KlaytnWalletKey} string. * * @example * const keyring = caver.wallet.keyring.createFromKlaytnWalletKey('0x{private key}0x{type}0x{address in hex}') * * @param {string} klaytnWalletKey The KlaytnWalletKey string. * @return {SingleKeyring} The SingleKeyring instance is returned. */ 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 {@link SingleKeyring} instance from an address and a private key string. * * @example * const keyring = caver.wallet.keyring.createWithSingleKey('0x{address in hex}', '0x{private key}') * * @param {string} address An address to be used for creating a keyring. * @param {string} key A private key string. * @return {SingleKeyring} The {@link SingleKeyring} instance is returned. */ 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 {@link MultipleKeyring} instance from an address and private key strings. * * @example * const keyring = caver.wallet.keyring.createWithMultipleKey('0x{address in hex}', ['0x{private key1}', '0x{private key2}' ]) * * @param {string} address An address of keyring. * @param {Array.<string>} keyArray An array of private key strings. * @return {MultipleKeyring} The {@link MultipleKeyring} instance is returned. */ 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 {@link RoleBasedKeyring} instance from an address and a 2D array of which each array element contains keys defined for each {@link https://docs.klaytn.com/klaytn/design/accounts#roles|role}. * * @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} The {@link RoleBasedKeyring} instance is returned. */ 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) } /** * An object that defines encrypted keystore. * * @typedef {object} KeyringFactory.Keystore * @property {number} version - The version number of the keystore. * @property {string} id - The id in the keystore. * @property {string} address - The address in the encrypted keyring. * @property {object} [crypto] - The encrypted private key for v3. * @property {Array.<object>|Array.<Array.<object>>} [keyring] - The encrypted private key(s) for v4. */ /** * Decrypts a keystore v3 or v4 JSON and returns the decrypted Keyring instance. * * @example * // Decrypt keystroe v4 (encrypted single keyring) * const decrypted = caver.wallet.keyring.decrypt({ * version: 4, * id: '9c12de05-0153-41c7-a8b7-849472eb5de7', * address: '0xc02cec4d0346bf4124deeb55c5216a4138a40a8c', * keyring: [ * { * ciphertext: 'eacf496cea5e80eca291251b3743bf93cdbcf7072efc3a74efeaf518e2796b15', * cipherparams: { iv: 'd688a4319342e872cefcf51aef3ec2da' }, * cipher: 'aes-128-ctr', * kdf: 'scrypt', * kdfparams: { * dklen: 32, * salt: 'c3cee502c7157e0faa42386c6d666116ffcdf093c345166c502e23bc34e6ba40', * n: 4096, * r: 8, * p: 1 * }, * mac: '4b49574f3d3356fa0d04f73e07d5a2a6bbfdd185bedfa31f37f347bc98f2ef26' * } * ] * }, 'password') * * // Decrypt keystroe v4 (encrypted multiple keyring) * const decrypted = caver.wallet.keyring.decrypt({ * version: 4, * id: '55da3f9c-6444-4fc1-abfa-f2eabfc57501', * address: '0x86bce8c859f5f304aa30adb89f2f7b6ee5a0d6e2', * keyring: [ * { * ciphertext: '93dd2c777abd9b80a0be8e1eb9739cbf27c127621a5d3f81e7779e47d3bb22f6', * cipherparams: { iv: '84f90907f3f54f53d19cbd6ae1496b86' }, * cipher: 'aes-128-ctr', * kdf: 'scrypt', * kdfparams: { * dklen: 32, * salt: '69bf176a136c67a39d131912fb1e0ada4be0ed9f882448e1557b5c4233006e10', * n: 4096, * r: 8, * p: 1, * }, * mac: '8f6d1d234f4a87162cf3de0c7fb1d4a8421cd8f5a97b86b1a8e576ffc1eb52d2', * }, * { * ciphertext: '53d50b4e86b550b26919d9b8cea762cd3c637dfe4f2a0f18995d3401ead839a6', * cipherparams: { iv: 'd7a6f63558996a9f99e7daabd289aa2c' }, * cipher: 'aes-128-ctr', * kdf: 'scrypt', * kdfparams: { * dklen: 32, * salt: '966116898d90c3e53ea09e4850a71e16df9533c1f9e1b2e1a9edec781e1ad44f', * n: 4096, * r: 8, * p: 1, * }, * mac: 'bca7125e17565c672a110ace9a25755847d42b81aa7df4bb8f5ce01ef7213295', * }, * ], * }, 'password') * * // Decrypt keystroe v4 (encrypted role-based keyring) * const decrypted = caver.wallet.keyring.decrypt({ * version: 4, * id: '55da3f9c-6444-4fc1-abfa-f2eabfc57501', * address: '0x86bce8c859f5f304aa30adb89f2f7b6ee5a0d6e2', * keyring: [ * [ * { * ciphertext: '93dd2c777abd9b80a0be8e1eb9739cbf27c127621a5d3f81e7779e47d3bb22f6', * cipherparams: { iv: '84f90907f3f54f53d19cbd6ae1496b86' }, * cipher: 'aes-128-ctr', * kdf: 'scrypt', * kdfparams: { * dklen: 32, * salt: '69bf176a136c67a39d131912fb1e0ada4be0ed9f882448e1557b5c4233006e10', * n: 4096, * r: 8, * p: 1, * }, * mac: '8f6d1d234f4a87162cf3de0c7fb1d4a8421cd8f5a97b86b1a8e576ffc1eb52d2', * }, * { * ciphertext: '53d50b4e86b550b26919d9b8cea762cd3c637dfe4f2a0f18995d3401ead839a6', * cipherparams: { iv: 'd7a6f63558996a9f99e7daabd289aa2c' }, * cipher: 'aes-128-ctr', * kdf: 'scrypt', * kdfparams: { * dklen: 32, * salt: '966116898d90c3e53ea09e4850a71e16df9533c1f9e1b2e1a9edec781e1ad44f', * n: 4096, * r: 8, * p: 1, * }, * mac: 'bca7125e17565c672a110ace9a25755847d42b81aa7df4bb8f5ce01ef7213295', * }, * ], * [ * { * ciphertext: 'f16def98a70bb2dae053f791882f3254c66d63416633b8d91c2848893e7876ce', * cipherparams: { iv: 'f5006128a4c53bc02cada64d095c15cf' }, * cipher: 'aes-128-ctr', * kdf: 'scrypt', * kdfparams: { * dklen: 32, * salt: '0d8a2f71f79c4880e43ff0795f6841a24cb18838b3ca8ecaeb0cda72da9a72ce', * n: 4096, * r: 8, * p: 1, * }, * mac: '38b79276c3805b9d2ff5fbabf1b9d4ead295151b95401c1e54aed782502fc90a', * }, * ], * [ * { * ciphertext: '544dbcc327942a6a52ad6a7d537e4459506afc700a6da4e8edebd62fb3dd55ee', * cipherparams: { iv: '05dd5d25ad6426e026818b6fa9b25818' }, * cipher: 'aes-128-ctr', * kdf: 'scrypt', * kdfparams: { * dklen: 32, * salt: '3a9003c1527f65c772c54c6056a38b0048c2e2d58dc0e584a1d867f2039a25aa', * n: 4096, * r: 8, * p: 1, * }, * mac: '19a698b51409cc9ac22d63d329b1201af3c89a04a1faea3111eec4ca97f2e00f', * }, * { * ciphertext: 'dd6b920f02cbcf5998ed205f8867ddbd9b6b088add8dfe1774a9fda29ff3920b', * cipherparams: { iv: 'ac04c0f4559dad80dc86c975d1ef7067' }, * cipher: 'aes-128-ctr', * kdf: 'scrypt', * kdfparams: { * dklen: 32, * salt: '22279c6dbcc706d7daa120022a236cfe149496dca8232b0f8159d1df999569d6', * n: 4096, * r: 8, * p: 1, * }, * mac: '1c54f7378fa279a49a2f790a0adb683defad8535a21bdf2f3dadc48a7bddf517', * }, * ], * ], * }, 'password') * * @param {KeyringFactory.KeystoreV4} keystore The keystore v3 or v4 to decrypt. * @param {string} password The password used for encryption. * @return {KeyringContainer.Keyring} The decrypted keyring ({@link SingleKeyring}, {@link MultipleKeyring} or {@link RoleBasedKeyring}) instance is returned. */ 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) } } /** * @type {typeof PrivateKey} * @example * caver.wallet.keyring.privateKey * */ KeyringFactory.privateKey = PrivateKey /** * @type {typeof SingleKeyring} * @example * caver.wallet.keyring.singleKeyring * */ KeyringFactory.singleKeyring = SingleKeyring /** * @type {typeof MultipleKeyring} * @example * caver.wallet.keyring.multipleKeyring * */ KeyringFactory.multipleKeyring = MultipleKeyring /** * @type {typeof RoleBasedKeyring} * @example * caver.wallet.keyring.roleBasedKeyring * */ KeyringFactory.roleBasedKeyring = RoleBasedKeyring /** * @type {typeof SignatureData} * @example * caver.wallet.keyring.signatureData */ KeyringFactory.signatureData = SignatureData KeyringFactory.role = KEY_ROLE module.exports = KeyringFactory