UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

458 lines 18.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.BlockchainStorageProvider = void 0; const uuid_1 = require("uuid"); const lru_cache_1 = require("lru-cache"); // Lazy-loaded dependencies let ethersModule; let cryptoModule; let ContractClient; let BatchOperationsManager; let RevocationMerkleTree; class BlockchainStorageProvider { constructor(config) { this.localKeyStore = new Map(); this.initialized = false; this.initPromise = null; if (!config.blockchain) { throw new Error('Blockchain configuration is required'); } this.config = config; } async initialize() { if (this.initialized) return; if (this.initPromise) return this.initPromise; this.initPromise = this.doInitialize(); await this.initPromise; this.initialized = true; } async doInitialize() { try { // Dynamic imports [ethersModule, cryptoModule] = await Promise.all([ Promise.resolve().then(() => __importStar(require('ethers'))), Promise.resolve().then(() => __importStar(require('crypto'))), ]); // Import blockchain components const [contractClientModule, batchOpsModule] = await Promise.all([ Promise.resolve().then(() => __importStar(require('../../blockchain/contract-client-lazy'))), Promise.resolve().then(() => __importStar(require('./blockchain-batch-operations'))), ]); ContractClient = contractClientModule.ContractClient; BatchOperationsManager = batchOpsModule.BatchOperationsManager; RevocationMerkleTree = batchOpsModule.RevocationMerkleTree; // Initialize contract client this.contractClient = new ContractClient(this.config.blockchain.rpcUrl, this.config.blockchain.privateKey, this.config.blockchain.contracts); // Initialize cache if enabled if (this.config.cache?.enabled) { this.cache = new lru_cache_1.LRUCache({ maxSize: this.config.cache.maxSize * 1024 * 1024, ttl: this.config.cache.ttl * 1000, sizeCalculation: (value) => JSON.stringify(value).length, }); } // Initialize batch manager this.batchManager = new BatchOperationsManager(10, 5000); } catch (error) { throw new Error(`Failed to initialize blockchain storage: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Cache helpers getCached(key) { if (!this.cache) return null; const entry = this.cache.get(key); if (!entry) return null; // Check if entry is still valid within TTL const now = Date.now(); if (now - entry.timestamp > (this.config.cache?.ttl || 0) * 1000) { this.cache.delete(key); return null; } return entry.data; } setCached(key, data) { if (!this.cache) return; this.cache.set(key, { data, timestamp: Date.now(), }); } // Encryption helpers for sensitive data encrypt(data, key) { if (!cryptoModule) throw new Error('Crypto module not loaded'); const algorithm = 'aes-256-gcm'; const salt = cryptoModule.randomBytes(16); const derivedKey = cryptoModule.pbkdf2Sync(key, salt, 100000, 32, 'sha256'); const iv = cryptoModule.randomBytes(16); const cipher = cryptoModule.createCipheriv(algorithm, derivedKey, iv); let encrypted = cipher.update(data, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return JSON.stringify({ encrypted, salt: salt.toString('hex'), iv: iv.toString('hex'), authTag: authTag.toString('hex'), }); } decrypt(encryptedData, key) { if (!cryptoModule) throw new Error('Crypto module not loaded'); const { encrypted, salt, iv, authTag } = JSON.parse(encryptedData); const algorithm = 'aes-256-gcm'; const derivedKey = cryptoModule.pbkdf2Sync(key, Buffer.from(salt, 'hex'), 100000, 32, 'sha256'); const decipher = cryptoModule.createDecipheriv(algorithm, derivedKey, Buffer.from(iv, 'hex')); decipher.setAuthTag(Buffer.from(authTag, 'hex')); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } // DID Operations async storeDID(did, document) { await this.initialize(); // Check cache first const cacheKey = `did:${did}`; try { // Store on blockchain const tx = await this.contractClient.registerDID(did, JSON.stringify(document)); await tx.wait(); // Cache the result this.setCached(cacheKey, document); } catch (error) { // Check if DID already exists and try to update instead if (error instanceof Error && error.message.includes('DID already exists')) { const updateTx = await this.contractClient.updateDID(did, JSON.stringify(document)); await updateTx.wait(); this.setCached(cacheKey, document); } else { throw error; } } } async resolveDID(did) { await this.initialize(); // Check cache first const cacheKey = `did:${did}`; const cached = this.getCached(cacheKey); if (cached) return cached; try { const documentJson = await this.contractClient.resolveDID(did); if (!documentJson || documentJson === '') return null; const document = JSON.parse(documentJson); // Cache the result this.setCached(cacheKey, document); return document; } catch (error) { console.error('Error resolving DID:', error); return null; } } async listDIDs(owner) { await this.initialize(); // Note: This is a limitation of the current blockchain implementation // In a real implementation, we would need to emit events when DIDs are created // and index them off-chain, or implement an enumerable mapping in the contract console.warn('listDIDs is not fully implemented for blockchain storage'); return []; } // Credential Operations async storeCredential(credential) { await this.initialize(); // For credentials, we store a hash on-chain and the full data off-chain (IPFS or encrypted local) const credentialHash = ethersModule.keccak256(ethersModule.toUtf8Bytes(JSON.stringify(credential))); // Store hash on blockchain for verification const holder = credential.credentialSubject.id; const issuer = credential.issuer; // Store credential data locally (encrypted) const encryptionKey = 'default-encryption-key'; const encryptedCredential = this.encrypt(JSON.stringify(credential), encryptionKey); // Use a simple key-value store approach const storageKey = `credential:${credential.id}`; this.localKeyStore.set(storageKey, encryptedCredential); // Also index by holder const holderKey = `holder:${holder}:credentials`; const existingCreds = this.localKeyStore.get(holderKey) || '[]'; const credIds = JSON.parse(existingCreds); if (!credIds.includes(credential.id)) { credIds.push(credential.id); this.localKeyStore.set(holderKey, JSON.stringify(credIds)); } // Cache the credential this.setCached(`credential:${credential.id}`, credential); } async getCredential(id) { await this.initialize(); // Check cache first const cacheKey = `credential:${id}`; const cached = this.getCached(cacheKey); if (cached) return cached; // Retrieve from local encrypted storage const storageKey = `credential:${id}`; const encryptedData = this.localKeyStore.get(storageKey); if (!encryptedData) return null; try { const encryptionKey = 'default-encryption-key'; const decryptedData = this.decrypt(encryptedData, encryptionKey); const credential = JSON.parse(decryptedData); // Cache the result this.setCached(cacheKey, credential); return credential; } catch (error) { console.error('Error retrieving credential:', error); return null; } } async listCredentials(holder) { await this.initialize(); const holderKey = `holder:${holder}:credentials`; const credIdsJson = this.localKeyStore.get(holderKey); if (!credIdsJson) return []; const credIds = JSON.parse(credIdsJson); const credentials = []; for (const credId of credIds) { const credential = await this.getCredential(credId); if (credential) { credentials.push(credential); } } return credentials; } async deleteCredential(id) { await this.initialize(); const credential = await this.getCredential(id); if (credential) { // Remove from holder index const holder = credential.credentialSubject.id; const holderKey = `holder:${holder}:credentials`; const credIdsJson = this.localKeyStore.get(holderKey); if (credIdsJson) { const credIds = JSON.parse(credIdsJson); const updatedIds = credIds.filter((cid) => cid !== id); this.localKeyStore.set(holderKey, JSON.stringify(updatedIds)); } // Remove credential this.localKeyStore.delete(`credential:${id}`); // Remove from cache if (this.cache) { this.cache.delete(`credential:${id}`); } } } // Revocation Operations async publishRevocation(issuerDID, revocationList) { await this.initialize(); // Generate a list ID based on timestamp const listId = `revocation-${Date.now()}`; // Use batch manager for efficient revocation updates await this.batchManager.addRevocation(issuerDID, listId, revocationList.revokedCredentialIds, async (issuer, listId, credIds) => { const tx = await this.contractClient.publishRevocationList(issuer, listId, credIds); await tx.wait(); }); // Cache the revocation list this.setCached(`revocation:${issuerDID}:${listId}`, revocationList); } async checkRevocation(issuerDID, credentialId) { await this.initialize(); // Check cache first const lists = await this.getRevocationListsForIssuer(issuerDID); for (const list of lists) { if (list.revokedCredentialIds.includes(credentialId)) { return true; } } // Check on-chain return await this.contractClient.isCredentialRevoked(issuerDID, credentialId); } async getRevocationList(issuerDID) { await this.initialize(); // Get the latest revocation list for the issuer const lists = await this.getRevocationListsForIssuer(issuerDID); return lists.length > 0 ? lists[0] : null; } async getRevocationListsForIssuer(issuerDID) { // In a real implementation, we would query events or maintain an index // For now, return cached lists const lists = []; if (this.cache) { for (const [key, entry] of this.cache.entries()) { if (key.startsWith(`revocation:${issuerDID}:`)) { lists.push(entry.data); } } } return lists; } // Key Management async storeKeyPair(identifier, encryptedKeyPair) { await this.initialize(); // Keys are always stored locally, never on-chain this.localKeyStore.set(`keypair:${identifier}`, encryptedKeyPair); } async retrieveKeyPair(identifier) { await this.initialize(); return this.localKeyStore.get(`keypair:${identifier}`) || null; } async deleteKeyPair(identifier) { await this.initialize(); this.localKeyStore.delete(`keypair:${identifier}`); } // Schema Operations async registerSchema(schema) { await this.initialize(); const schemaId = schema.id || `schema:${(0, uuid_1.v4)()}`; const schemaWithId = { ...schema, id: schemaId }; // Store schema on blockchain const tx = await this.contractClient.registerSchema(schemaId, JSON.stringify(schemaWithId)); await tx.wait(); // Cache the schema this.setCached(`schema:${schemaId}`, schemaWithId); return schemaId; } async getSchema(schemaId) { await this.initialize(); // Check cache first const cacheKey = `schema:${schemaId}`; const cached = this.getCached(cacheKey); if (cached) return cached; try { const schemaJson = await this.contractClient.getSchema(schemaId); if (!schemaJson || schemaJson === '') return null; const schema = JSON.parse(schemaJson); // Cache the result this.setCached(cacheKey, schema); return schema; } catch (error) { console.error('Error retrieving schema:', error); return null; } } async listSchemas(issuerDID) { await this.initialize(); // This would require event indexing in a real implementation console.warn('listSchemas is not fully implemented for blockchain storage'); return []; } // General operations async clear() { await this.initialize(); // Clear local storage and cache this.localKeyStore.clear(); if (this.cache) { this.cache.clear(); } // Note: We cannot clear blockchain data console.warn('Blockchain data cannot be cleared'); } // Utility methods async getStorageStats() { await this.initialize(); const blockNumber = await this.contractClient.getBlockNumber(); return { localItems: this.localKeyStore.size, cacheSize: this.cache ? this.cache.size : 0, blockNumber, }; } // Phone Number Operations async storePhoneNumber(userDID, phoneNumber) { throw new Error('Phone number storage not implemented in blockchain provider'); } async getPhoneNumber(userDID, phoneId) { throw new Error('Phone number retrieval not implemented in blockchain provider'); } async listPhoneNumbers(userDID) { throw new Error('Phone number listing not implemented in blockchain provider'); } async updatePhoneNumber(userDID, phoneId, phoneNumber) { throw new Error('Phone number update not implemented in blockchain provider'); } async deletePhoneNumber(userDID, phoneId) { throw new Error('Phone number deletion not implemented in blockchain provider'); } // Address Operations async storeAddress(userDID, address) { throw new Error('Address storage not implemented in blockchain provider'); } async getAddress(userDID, addressId) { throw new Error('Address retrieval not implemented in blockchain provider'); } async listAddresses(userDID) { throw new Error('Address listing not implemented in blockchain provider'); } async updateAddress(userDID, addressId, address) { throw new Error('Address update not implemented in blockchain provider'); } async deleteAddress(userDID, addressId) { throw new Error('Address deletion not implemented in blockchain provider'); } // Email Address Operations async storeEmailAddress(userDID, emailAddress) { throw new Error('Email address storage not implemented in blockchain provider'); } async getEmailAddress(userDID, emailId) { throw new Error('Email address storage not implemented in blockchain provider'); } async listEmailAddresses(userDID) { throw new Error('Email address storage not implemented in blockchain provider'); } async updateEmailAddress(userDID, emailId, emailAddress) { throw new Error('Email address storage not implemented in blockchain provider'); } async deleteEmailAddress(userDID, emailId) { throw new Error('Email address storage not implemented in blockchain provider'); } } exports.BlockchainStorageProvider = BlockchainStorageProvider; //# sourceMappingURL=blockchain-storage-provider-lazy.js.map