UNPKG

six-caver-js

Version:

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

181 lines (158 loc) 7.93 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 RLP = require('eth-lib/lib/rlp') const AccountKeyWeightedMultiSig = require('./accountKeyWeightedMultiSig') const AccountKeyLegacy = require('./accountKeyLegacy') const AccountKeyPublic = require('./accountKeyPublic') const AccountKeyFail = require('./accountKeyFail') const utils = require('../../../caver-utils') const { ACCOUNT_KEY_TAG } = require('./accountKeyHelper') const { KEY_ROLE } = require('../../../caver-wallet/src/keyring/keyringHelper') const WeightedMultiSigOptions = require('./weightedMultiSigOptions') function isValidRoleBasedKeyFormat(roleBasedAccountKeys) { if (!_.isArray(roleBasedAccountKeys)) return false if (roleBasedAccountKeys.length > KEY_ROLE.roleLast) return false for (const accountKey of roleBasedAccountKeys) { if ( accountKey !== undefined && !(accountKey instanceof AccountKeyLegacy) && !(accountKey instanceof AccountKeyPublic) && !(accountKey instanceof AccountKeyFail) && !(accountKey instanceof AccountKeyWeightedMultiSig) ) { return false } } return true } /** * Representing an AccountKeyRoleBased. * @class */ class AccountKeyRoleBased { /** * Decodes an RLP-encoded AccountKeyRoleBased string. * @param {string} rlpEncodedKey - An RLP-encoded AccountKeyRoleBased string. * @return {AccountKeyRoleBased} */ static decode(rlpEncodedKey) { rlpEncodedKey = utils.addHexPrefix(rlpEncodedKey) if (!rlpEncodedKey.startsWith(ACCOUNT_KEY_TAG.ACCOUNT_KEY_ROLE_BASED_TAG)) throw new Error( `Cannot decode to AccountKeyRoleBased. The prefix must be ${ACCOUNT_KEY_TAG.ACCOUNT_KEY_ROLE_BASED_TAG}: ${rlpEncodedKey}` ) const keys = RLP.decode(`0x${rlpEncodedKey.slice(ACCOUNT_KEY_TAG.ACCOUNT_KEY_ROLE_BASED_TAG.length)}`) const accountKeys = [] for (const key of keys) { if (key.startsWith(ACCOUNT_KEY_TAG.ACCOUNT_KEY_ROLE_BASED_TAG)) throw new Error('Nested role based key.') if (key.startsWith(ACCOUNT_KEY_TAG.ACCOUNT_KEY_NIL_TAG)) { accountKeys.push(undefined) } else if (key.startsWith(ACCOUNT_KEY_TAG.ACCOUNT_KEY_LEGACY_TAG)) { accountKeys.push(AccountKeyLegacy.decode(key)) } else if (key.startsWith(ACCOUNT_KEY_TAG.ACCOUNT_KEY_PUBLIC_TAG)) { accountKeys.push(AccountKeyPublic.decode(key)) } else if (key.startsWith(ACCOUNT_KEY_TAG.ACCOUNT_KEY_FAIL_TAG)) { accountKeys.push(AccountKeyFail.decode(key)) } else if (key.startsWith(ACCOUNT_KEY_TAG.ACCOUNT_KEY_WEIGHTED_MULTISIG_TAG)) { accountKeys.push(AccountKeyWeightedMultiSig.decode(key)) } else { throw new Error(`Failed to decode RLP-encoded account key. Invalid RLP-encoded account key ${key}`) } } return new AccountKeyRoleBased(accountKeys) } /** * Creates an instance of AccountKeyRoleBased. * @param {Array.<AccountKeyLegacy|AccountKeyFail|Array.<string>>} roleBasedPubArray - An array of public key strings. * @param {Array.<WeightedMultiSigOptions|object>} options - An array of options which defines threshold and weight. * @return {AccountKeyRoleBased} */ static fromRoleBasedPublicKeysAndOptions(roleBasedPubArray, options) { if (!options) options = Array(KEY_ROLE.roleLast).fill(new WeightedMultiSigOptions()) const accountKeys = [] // Format will be like below // keyArray = [[pub, pub], [pub], [pub, pub, pub]] // keyArray = [[accountKeyLegacy], [accountKeyFail], [pub, pub, pub]] // keyArray = [['legacy'], ['fail'], []] // options = [{threshold: 1, weights: [1,1]}, {}, {threshold: 1, weights: [1,1,1]}] for (let i = 0; i < roleBasedPubArray.length; i++) { if (!(options[i] instanceof WeightedMultiSigOptions)) options[i] = WeightedMultiSigOptions.fromObject(options[i]) // To handle instance of AccountKeyLegacy or AccountKeyFail if (!_.isArray(roleBasedPubArray[i])) { throw new Error(`Invalid format of keys: Each role should define the key to use in an array form.`) } // Empty key array means AccountKeyNil if (roleBasedPubArray[i].length === 0) { if (!options[i].isEmpty()) throw new Error(`Invalid options: AccountKeyNil cannot have options.`) accountKeys.push(undefined) continue } if (roleBasedPubArray[i].length === 1) { if ( roleBasedPubArray[i][0] instanceof AccountKeyLegacy || roleBasedPubArray[i][0] instanceof AccountKeyFail || roleBasedPubArray[i][0] === 'legacy' || roleBasedPubArray[i][0] === 'fail' ) { if (!options[i].isEmpty()) throw new Error(`Invalid options: AccountKeyLegacy or AccountKeyFail cannot have options.`) if (roleBasedPubArray[i][0] === 'legacy') roleBasedPubArray[i][0] = new AccountKeyLegacy() if (roleBasedPubArray[i][0] === 'fail') roleBasedPubArray[i][0] = new AccountKeyFail() accountKeys.push(roleBasedPubArray[i][0]) continue } if (options[i].isEmpty()) { accountKeys.push(AccountKeyPublic.fromPublicKey(roleBasedPubArray[i][0])) continue } } accountKeys.push(AccountKeyWeightedMultiSig.fromPublicKeysAndOptions(roleBasedPubArray[i], options[i])) } return new AccountKeyRoleBased(accountKeys) } /** * Create an instance of AccountKeyRoleBased. * @param {Array.<AccountKeyLegacy|AccountKeyPublic|AccountKeyFail|AccountKeyWeightedMultiSig>} accountKeyArray - An array containing arrays of instances of AccountKeyPublic or AccountKeyWeightedMultiSig for each role. */ constructor(accountKeyArray) { this.accountKeys = accountKeyArray } /** * @type {Array.<AccountKeyLegacy|AccountKeyPublic|AccountKeyFail|AccountKeyWeightedMultiSig>} */ get accountKeys() { return this._accountKeys } set accountKeys(keys) { if (!isValidRoleBasedKeyFormat(keys)) throw new Error(`Invalid role-based account key format.`) this._accountKeys = keys } /** * Returns an RLP-encoded AccountKeyRoleBased string. * @return {string} */ getRLPEncoding() { const encodedAccountKeys = [] for (const accountKey of this.accountKeys) { if (accountKey === undefined) { encodedAccountKeys.push(ACCOUNT_KEY_TAG.ACCOUNT_KEY_NIL_TAG) continue } encodedAccountKeys.push(accountKey.getRLPEncoding()) } return ACCOUNT_KEY_TAG.ACCOUNT_KEY_ROLE_BASED_TAG + RLP.encode(encodedAccountKeys).slice(2) } } module.exports = AccountKeyRoleBased