UNPKG

anon-identity

Version:

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

365 lines 15.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BlockchainStorageProvider = void 0; const ethers_1 = require("ethers"); const uuid_1 = require("uuid"); const lru_cache_1 = require("lru-cache"); // Import contract ABIs and client const contract_client_1 = require("../../blockchain/contract-client"); const blockchain_batch_operations_1 = require("./blockchain-batch-operations"); class BlockchainStorageProvider { constructor(config) { this.localKeyStore = new Map(); // Keys are always stored locally if (!config.blockchain) { throw new Error('Blockchain configuration is required'); } this.config = config; // Initialize contract client this.contractClient = new contract_client_1.ContractClient(config.blockchain.rpcUrl, config.blockchain.privateKey, config.blockchain.contracts); // Initialize cache if enabled if (config.cache?.enabled) { this.cache = new lru_cache_1.LRUCache({ maxSize: config.cache.maxSize * 1024 * 1024, // Convert MB to bytes ttl: config.cache.ttl * 1000, // Convert seconds to milliseconds sizeCalculation: (value) => JSON.stringify(value).length, }); } // Initialize batch manager for gas optimization this.batchManager = new blockchain_batch_operations_1.BatchOperationsManager(10, 5000); } // Cache helper methods getCached(key) { if (!this.cache) return null; const entry = this.cache.get(key); if (!entry) return null; 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(), }); } // DID Operations async storeDID(did, document) { // Convert DIDDocument to blockchain format const publicKey = document.verificationMethod?.[0]?.publicKeyMultibase || ''; const documentHash = this.hashDocument(document); await this.contractClient.registerDID(did, publicKey, documentHash); // Store full document in IPFS if available if (this.ipfsClient && document) { // TODO: Implement IPFS storage } // Clear cache for this DID this.cache?.delete(`did:${did}`); } async resolveDID(did) { // Check cache first const cached = this.getCached(`did:${did}`); if (cached) return cached; try { const didInfo = await this.contractClient.resolveDID(did); if (!didInfo || !didInfo.active) return null; // Reconstruct DIDDocument from blockchain data const document = { '@context': ['https://www.w3.org/ns/did/v1'], id: did, verificationMethod: [{ id: `${did}#key-1`, type: 'Ed25519VerificationKey2020', controller: did, publicKeyMultibase: didInfo.publicKey, }], authentication: [`${did}#key-1`], assertionMethod: [`${did}#key-1`], created: new Date(didInfo.createdAt * 1000).toISOString(), updated: new Date(didInfo.updatedAt * 1000).toISOString(), }; // Cache the result this.setCached(`did:${did}`, document); return document; } catch (error) { console.error('Error resolving DID from blockchain:', error); return null; } } async listDIDs(owner) { // This operation is expensive on blockchain, use events try { const events = await this.contractClient.queryDIDEvents({ owner, fromBlock: 0, toBlock: 'latest', }); const dids = events .filter(event => event.eventName === 'DIDRegistered') .map(event => event.args?.did) .filter(did => did !== undefined); return [...new Set(dids)]; // Remove duplicates } catch (error) { console.error('Error listing DIDs from blockchain:', error); return []; } } // Credential Operations async storeCredential(credential) { // Store credential hash on blockchain for integrity const credentialHash = this.hashCredential(credential); const issuerDID = credential.issuer; // For now, we store credential metadata in the schema registry // In a production system, you might want a separate credential registry const schemaTypeEnum = 1; // Using 1 for credential records await this.contractClient.registerSchema(`credential:${credential.id}`, 'VerifiableCredential', credentialHash, issuerDID, '1.0', schemaTypeEnum, [] // dependencies ); // Store full credential in IPFS if available if (this.ipfsClient) { // TODO: Implement IPFS storage } // Clear cache this.cache?.delete(`credential:${credential.id}`); } async getCredential(id) { // Check cache first const cached = this.getCached(`credential:${id}`); if (cached) return cached; // For now, credentials are not fully stored on-chain // In a full implementation, we would retrieve from IPFS // using the hash stored on-chain console.warn('Full credential retrieval from blockchain not yet implemented'); return null; } async listCredentials(holder) { // This would require an index of credentials by holder // For now, return empty array console.warn('Listing credentials by holder not yet implemented for blockchain storage'); return []; } async deleteCredential(id) { // Credentials on blockchain are immutable, but we can revoke them console.warn('Credential deletion not supported on blockchain. Use revocation instead.'); } // Revocation Operations async publishRevocation(issuerDID, revocationList) { // Convert credential IDs to hashes const credentialHashes = revocationList.revokedCredentialIds.map(id => ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(id))); // Calculate merkle root for gas efficiency const merkleRoot = this.calculateMerkleRoot(credentialHashes); await this.contractClient.publishRevocationList(issuerDID, credentialHashes, revocationList.signature, merkleRoot); // Clear cache this.cache?.delete(`revocation:${issuerDID}`); } async checkRevocation(issuerDID, credentialId) { // Check cache first const cacheKey = `revocation:${issuerDID}:${credentialId}`; const cached = this.getCached(cacheKey); if (cached !== null) return cached; try { const credentialHash = ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(credentialId)); const isRevoked = await this.contractClient.checkRevocation(issuerDID, credentialHash); // Cache the result this.setCached(cacheKey, isRevoked); return isRevoked; } catch (error) { console.error('Error checking revocation:', error); return false; } } async getRevocationList(issuerDID) { // Check cache first const cached = this.getCached(`revocation:${issuerDID}`); if (cached) return cached; try { const revocationInfo = await this.contractClient.getRevocationList(issuerDID); if (!revocationInfo) return null; // Convert back to RevocationList format const revocationList = { issuerDID, revokedCredentialIds: [], // Would need to be retrieved from events or IPFS timestamp: revocationInfo.timestamp, signature: revocationInfo.signature, }; // Cache the result this.setCached(`revocation:${issuerDID}`, revocationList); return revocationList; } catch (error) { console.error('Error getting revocation list:', error); return null; } } // Key Management (always local) async storeKeyPair(identifier, encryptedKeyPair) { // Keys are never stored on blockchain for security this.localKeyStore.set(identifier, encryptedKeyPair); } async retrieveKeyPair(identifier) { return this.localKeyStore.get(identifier) || null; } async deleteKeyPair(identifier) { this.localKeyStore.delete(identifier); } // Schema Operations async registerSchema(schema) { const schemaId = schema.id || `schema:${(0, uuid_1.v4)()}`; const schemaHash = this.hashSchema(schema); // Convert schema type to enum value (0 = VerifiableCredential) const schemaTypeEnum = 0; // SchemaType.VerifiableCredential await this.contractClient.registerSchema(schema.name, schema.description, schemaHash, schema.issuerDID, schema.version, schemaTypeEnum, [] // dependencies ); // Store full schema in IPFS if available if (this.ipfsClient) { // TODO: Implement IPFS storage } // Clear cache this.cache?.delete(`schema:${schemaId}`); return schemaId; } async getSchema(schemaId) { // Check cache first const cached = this.getCached(`schema:${schemaId}`); if (cached) return cached; try { // Schema ID on blockchain might be different, try to map it const schemaInfo = await this.contractClient.getSchema(schemaId); if (!schemaInfo) return null; // Reconstruct CredentialSchema from blockchain data const schema = { id: schemaId, name: schemaInfo.name, description: schemaInfo.description, properties: {}, // Would need to be retrieved from IPFS issuerDID: schemaInfo.issuerDID, version: schemaInfo.version, active: schemaInfo.active, }; // Cache the result this.setCached(`schema:${schemaId}`, schema); return schema; } catch (error) { console.error('Error getting schema from blockchain:', error); return null; } } async listSchemas(issuerDID) { try { const events = await this.contractClient.querySchemaEvents({ issuerDID, fromBlock: 0, toBlock: 'latest', }); const schemas = []; for (const event of events) { if (event.eventName === 'SchemaRegistered') { const schemaId = event.args?.schemaId; if (schemaId) { const schema = await this.getSchema(schemaId.toString()); if (schema) { schemas.push(schema); } } } } return schemas; } catch (error) { console.error('Error listing schemas from blockchain:', error); return []; } } // General operations async clear() { // Cannot clear blockchain data console.warn('Clear operation not supported for blockchain storage'); // Clear local data only this.localKeyStore.clear(); this.cache?.clear(); } // Helper methods hashDocument(document) { const canonicalDoc = JSON.stringify(document, Object.keys(document).sort()); return ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(canonicalDoc)); } hashCredential(credential) { const canonicalCred = JSON.stringify(credential, Object.keys(credential).sort()); return ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(canonicalCred)); } hashSchema(schema) { const canonicalSchema = JSON.stringify(schema, Object.keys(schema).sort()); return ethers_1.ethers.keccak256(ethers_1.ethers.toUtf8Bytes(canonicalSchema)); } calculateMerkleRoot(hashes) { const merkleTree = new blockchain_batch_operations_1.RevocationMerkleTree(hashes); return merkleTree.getRoot(); } // Phone Number Operations (not implemented for blockchain) async storePhoneNumber(userDID, phoneNumber) { throw new Error('Phone number storage not implemented for blockchain provider'); } async getPhoneNumber(userDID, phoneId) { throw new Error('Phone number storage not implemented for blockchain provider'); } async listPhoneNumbers(userDID) { throw new Error('Phone number storage not implemented for blockchain provider'); } async updatePhoneNumber(userDID, phoneId, phoneNumber) { throw new Error('Phone number storage not implemented for blockchain provider'); } async deletePhoneNumber(userDID, phoneId) { throw new Error('Phone number storage not implemented for blockchain provider'); } // Address Operations (not implemented for blockchain) async storeAddress(userDID, address) { throw new Error('Address storage not implemented for blockchain provider'); } async getAddress(userDID, addressId) { throw new Error('Address storage not implemented for blockchain provider'); } async listAddresses(userDID) { throw new Error('Address storage not implemented for blockchain provider'); } async updateAddress(userDID, addressId, address) { throw new Error('Address storage not implemented for blockchain provider'); } async deleteAddress(userDID, addressId) { throw new Error('Address storage not implemented for blockchain provider'); } // Email Address Operations (not implemented for blockchain) 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.js.map