UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

218 lines 9.73 kB
import { PrivateKey, PublicKey } from '../primitives/index.js'; import { KeyDeriver } from './KeyDeriver.js'; /** * A cached version of KeyDeriver that caches the results of key derivation methods. * This is useful for optimizing performance when the same keys are derived multiple times. * It supports configurable cache size with sane defaults and maintains cache entries using LRU (Least Recently Used) eviction policy. */ export default class CachedKeyDeriver { keyDeriver; cache; maxCacheSize; /** * The root key from which all other keys are derived. */ rootKey; /** * The identity of this key deriver which is normally the public key associated with the `rootKey` */ identityKey; /** * Initializes the CachedKeyDeriver instance with a root private key and optional cache settings. * @param {PrivateKey | 'anyone'} rootKey - The root private key or the string 'anyone'. * @param {Object} [options] - Optional settings for the cache. * @param {number} [options.maxCacheSize=1000] - The maximum number of entries to store in the cache. */ constructor(rootKey, options) { if (rootKey === 'anyone') { this.rootKey = new PrivateKey(1); } else { this.rootKey = rootKey; } this.keyDeriver = new KeyDeriver(this.rootKey, (priv, pub, point) => { this.cacheSet(`${priv.toString()}-${pub.toString()}`, point); }, (priv, pub) => { return this.cacheGet(`${priv.toString()}-${pub.toString()}`); }); this.identityKey = this.rootKey.toPublicKey().toString(); this.cache = new Map(); const maxCacheSize = options?.maxCacheSize; this.maxCacheSize = (maxCacheSize != null && !isNaN(maxCacheSize) && maxCacheSize > 0) ? maxCacheSize : 1000; } /** * Derives a public key based on protocol ID, key ID, and counterparty. * Caches the result for future calls with the same parameters. * @param {WalletProtocol} protocolID - The protocol ID including a security level and protocol name. * @param {string} keyID - The key identifier. * @param {Counterparty} counterparty - The counterparty's public key or a predefined value ('self' or 'anyone'). * @param {boolean} [forSelf=false] - Whether deriving for self. * @returns {PublicKey} - The derived public key. */ derivePublicKey(protocolID, keyID, counterparty, forSelf = false) { const cacheKey = this.generateCacheKey('derivePublicKey', protocolID, keyID, counterparty, forSelf); if (this.cache.has(cacheKey)) { const cachedValue = this.cacheGet(cacheKey); if (cachedValue === undefined) { throw new Error('Cached value is undefined'); } return cachedValue; } else { const result = this.keyDeriver.derivePublicKey(protocolID, keyID, counterparty, forSelf); this.cacheSet(cacheKey, result); return result; } } /** * Derives a private key based on protocol ID, key ID, and counterparty. * Caches the result for future calls with the same parameters. * @param {WalletProtocol} protocolID - The protocol ID including a security level and protocol name. * @param {string} keyID - The key identifier. * @param {Counterparty} counterparty - The counterparty's public key or a predefined value ('self' or 'anyone'). * @returns {PrivateKey} - The derived private key. */ derivePrivateKey(protocolID, keyID, counterparty) { const cacheKey = this.generateCacheKey('derivePrivateKey', protocolID, keyID, counterparty); if (this.cache.has(cacheKey)) { const cachedValue = this.cacheGet(cacheKey); if (cachedValue === undefined) { throw new Error('Cached value is undefined'); } return cachedValue; } else { const result = this.keyDeriver.derivePrivateKey(protocolID, keyID, counterparty); this.cacheSet(cacheKey, result); return result; } } /** * Derives a symmetric key based on protocol ID, key ID, and counterparty. * Caches the result for future calls with the same parameters. * @param {WalletProtocol} protocolID - The protocol ID including a security level and protocol name. * @param {string} keyID - The key identifier. * @param {Counterparty} counterparty - The counterparty's public key or a predefined value ('self' or 'anyone'). * @returns {SymmetricKey} - The derived symmetric key. * @throws {Error} - Throws an error if attempting to derive a symmetric key for 'anyone'. */ deriveSymmetricKey(protocolID, keyID, counterparty) { const cacheKey = this.generateCacheKey('deriveSymmetricKey', protocolID, keyID, counterparty); if (this.cache.has(cacheKey)) { const cachedValue = this.cacheGet(cacheKey); if (cachedValue === undefined) { throw new Error('Cached value is undefined'); } return cachedValue; } else { const result = this.keyDeriver.deriveSymmetricKey(protocolID, keyID, counterparty); this.cacheSet(cacheKey, result); return result; } } /** * Reveals the shared secret between the root key and the counterparty. * Caches the result for future calls with the same parameters. * @param {Counterparty} counterparty - The counterparty's public key or a predefined value ('self' or 'anyone'). * @returns {number[]} - The shared secret as a number array. * @throws {Error} - Throws an error if attempting to reveal a shared secret for 'self'. */ revealCounterpartySecret(counterparty) { const cacheKey = this.generateCacheKey('revealCounterpartySecret', counterparty); if (this.cache.has(cacheKey)) { const cachedValue = this.cacheGet(cacheKey); if (cachedValue === undefined) { throw new Error('Cached value is undefined'); } return cachedValue; } else { const result = this.keyDeriver.revealCounterpartySecret(counterparty); this.cacheSet(cacheKey, result); return result; } } /** * Reveals the specific key association for a given protocol ID, key ID, and counterparty. * Caches the result for future calls with the same parameters. * @param {Counterparty} counterparty - The counterparty's public key or a predefined value ('self' or 'anyone'). * @param {WalletProtocol} protocolID - The protocol ID including a security level and protocol name. * @param {string} keyID - The key identifier. * @returns {number[]} - The specific key association as a number array. */ revealSpecificSecret(counterparty, protocolID, keyID) { const cacheKey = this.generateCacheKey('revealSpecificSecret', counterparty, protocolID, keyID); if (this.cache.has(cacheKey)) { const cachedValue = this.cacheGet(cacheKey); if (cachedValue === undefined) { throw new Error('Cached value is undefined'); } return cachedValue; } else { const result = this.keyDeriver.revealSpecificSecret(counterparty, protocolID, keyID); this.cacheSet(cacheKey, result); return result; } } /** * Generates a unique cache key based on the method name and input parameters. * @param {string} methodName - The name of the method. * @param {...any} args - The arguments passed to the method. * @returns {string} - The generated cache key. */ generateCacheKey(methodName, ...args) { const serializedArgs = args .map((arg) => this.serializeArgument(arg)) .join('|'); return `${methodName}|${serializedArgs}`; } /** * Serializes an argument to a string for use in a cache key. * @param {any} arg - The argument to serialize. * @returns {string} - The serialized argument. */ serializeArgument(arg) { if (arg instanceof PublicKey || arg instanceof PrivateKey) { return arg.toString(); } else if (Array.isArray(arg)) { return arg.map((item) => this.serializeArgument(item)).join(','); } else if (typeof arg === 'object' && arg !== null) { return JSON.stringify(arg); } else { return String(arg); } } /** * Retrieves an item from the cache and updates its position to reflect recent use. * @param {string} cacheKey - The key of the cached item. * @returns {any} - The cached value. */ cacheGet(cacheKey) { const value = this.cache.get(cacheKey); // Update the entry to reflect recent use this.cache.delete(cacheKey); if (value !== undefined) { this.cache.set(cacheKey, value); } return value; } /** * Adds an item to the cache and evicts the least recently used item if necessary. * @param {string} cacheKey - The key of the item to cache. * @param {any} value - The value to cache. */ cacheSet(cacheKey, value) { if (this.cache.size >= this.maxCacheSize) { // Evict the least recently used item (first item in Map) const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(cacheKey, value); } } //# sourceMappingURL=CachedKeyDeriver.js.map