UNPKG

@crpdo/key

Version:

Streamlines key generation, derivation, and management through its simple and intuitive API

212 lines (197 loc) 6.97 kB
const { Crypto, _ } = require('@crpdo/crypto') const Nacl = require('@crpdo/crypto/nacl') const BaseKey = require('./base-key') const SignKey = require('./sign-key') const BoxKey = require('./box-key') const HdKey = require('./hd-key') /** * Key class represents a cryptographic key with additional properties and methods. * @class * * @property {BaseKey} Base - Base key * @property {SignKey} Sign - Sign key * @property {BoxKey} Box - Box key * @property {HdKey} Hd - Hierarchical deterministic key */ class Key { static get Base() { return BaseKey } static get Sign() { return SignKey } static get Box() { return BoxKey } static get Hd() { return HdKey } /** * Create a new instance of the Key class. * * @param {Object} [options={}] - Options to create the Key instance. * If options is a string, it is used as the seed. * Options can include properties like 'seed', 'list', 'dpath', 'dlist', 'path', 'index'. */ constructor(options = {}) { const show = false if (_.isString(options)) options = { seed: options } _.objProp(this, 'seed', options.seed || Key.generateSeed()) _.objProp(this, 'hd', new HdKey(this.seed)) _.objProp(this, 'sig', SignKey.fromHdKey(this.hd)) _.objProp(this, 'box', BoxKey.fromSignKey(this.sig)) _.objProp(this, 'id', this.sig.publicKey, { show: true }) _.objProp(this, 'list', options.list || [], { show }) _.objProp(this, 'dpath', options.dpath || 'm', { show }) _.objProp(this, 'dlist', options.dlist || [], { show }) _.objProp(this, 'path', options.path || 'm', { show: true }) _.objProp(this, 'index', options.index || 0, { show: true }) _.objProp(this, 'depth', this.hd.depth, { show: true }) _.objProp(this, 'currentIndex', 0, { writable: true, show: false }) _.objProp(this, 'keys', [], { show: true }) _.objProp(this, 'children', {}, { show: true }) } /** * Get the public key. * * @returns {string} Public key of the instance. */ get publicKey() { return this.sig.publicKey } /** * Get the private key. * * @returns {string} Private key of the instance. */ get privateKey() { return this.sig.privateKey } /** * Derives a new key from the current key. * * @param {string|number|Array} slug - The identifier of the new derived key. * @param {Object} [opts={}] - Additional options for the derivation. * @param {boolean} [isChild=true] - Whether the derived key is a child key. * @returns {Key} The derived key. */ derive(slug, opts = {}, isChild = true) { const parts = this._parsePath(slug) const keyType = isChild ? 'children' : 'keys' let node = this for (let ii = 0; ii < parts.length; ii++) { const partKey = parts[ii] if (!node[keyType][partKey]) { const { part, isHardened } = this._parsePathPart(partKey, node) const index = Number(part) const dpath = `m/${part}${isHardened ? "'" : ''}` const list = node.list.concat(partKey) const hd = node.hd.derive(dpath) const seed = hd.privateExtendedKey const dlist = node.dlist.concat(dpath.slice(2)) const path = `m/${dlist.join('/')}` let newOpts = { seed, list, dpath, dlist, path, index } if (ii === parts.length - 1) newOpts = { ...newOpts, ...opts } const newNode = new this.constructor(newOpts) node[keyType][partKey] = newNode node = newNode } else { node = node[keyType][partKey] } } return node } /** * Parses the provided slug into a key path. * * @private * @param {string|number|Array} slug - The slug to be parsed. Can be a string, a number, or an array. * @returns {Array} The parsed path as an array. */ _parsePath(slug) { if (_.isArray(slug)) return slug if (_.isInteger(slug)) slug = `${slug}'` if (slug[0].match(/^(m|M)/)) slug = slug.slice(1) if (slug.startsWith('/')) slug = slug.slice(1) return _.flatten(slug.split('/').map(p => String(p).split('.'))) } /** * Parses a part of the key path. * * @private * @param {string|number} part - The part of the path to parse. * @param {Object} node - The node associated with the part. * @returns {Object} An object containing the parsed part and a boolean indicating whether the part is hardened. */ _parsePathPart(part, node) { if (_.isInteger(part)) part = `${part}'` const isHardened = part.endsWith("'") if (isHardened) part = part.slice(0, -1) if (!_.isInteger(Number(part))) part = Crypto.random.integer(node.seed + part) return { part, isHardened } } /** * Ratchets the keys based on the index and additional options. * * @param {number} index - The index to use for ratcheting. * @param {Object} [opts={}] - Additional options for the ratcheting. * @returns {Key} The key after ratcheting. */ ratchet(index, opts = {}) { const key = this.keys[index] if (key) return key if (_.isNil(index)) { index = this.currentIndex this.currentIndex++ } return this.derive(index, opts, false) } /** * Generates a new seed for key creation. * * @static * @returns {string} The generated seed. */ static generateSeed() { return Crypto.random.hash() } /** * Sign a data with the private key. * * @param {string|Buffer} data - The data to sign. * @returns {string} The signature of the data. */ sign(data) { return this.sig.sign(data) } /** * Verify a signature for a data with the given public key. * * @param {string|Buffer} data - The data to verify. * @param {string} signature - The signature to verify. * @param {string} publicKey - The public key to use for verification. * @returns {boolean} True if the signature is valid, false otherwise. */ verify(data, signature, publicKey) { return this.sig.verify(data, signature, publicKey) } /** * Encrypts a data with the given public key. * * @param {string|Buffer} data - The data to encrypt. * @param {string} publicKey - The public key to use for encryption. * @param {string} [nonce] - The nonce used for encryption. * @param {boolean} [encode] - Whether to encode the encrypted data. * @returns {string} The encrypted data. */ encrypt(data, publicKey, nonce, encode) { return this.box.encrypt(data, publicKey, nonce, encode) } /** * Decrypts a data with the given public key. * * @param {string|Buffer} data - The encrypted data to decrypt. * @param {string} publicKey - The public key to use for decryption. * @param {string} [nonce] - The nonce used for decryption. * @param {boolean} [encode] - Whether to encode the decrypted data. * @returns {string} The decrypted data. */ decrypt(data, publicKey, nonce, encode) { return this.box.decrypt(data, publicKey, nonce, encode) } } module.exports = Key