anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
458 lines • 18.6 kB
JavaScript
"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