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