@crpdo/key
Version:
Streamlines key generation, derivation, and management through its simple and intuitive API
212 lines (197 loc) • 6.97 kB
JavaScript
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