UNPKG

anon-identity

Version:

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

220 lines 8.51 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BbsSelectiveDisclosure = void 0; const bbs_bls_signature_2020_1 = require("../ld/signature-suites/bbs-bls-signature-2020"); const jsonld_processor_1 = require("../ld/jsonld-processor"); /** * BBS+ based selective disclosure for Verifiable Credentials */ class BbsSelectiveDisclosure { constructor(jsonLdProcessor) { this.jsonLdProcessor = jsonLdProcessor || new jsonld_processor_1.JsonLdProcessor(); this.suite = new bbs_bls_signature_2020_1.BbsBlsSignature2020Suite({ jsonLdProcessor: this.jsonLdProcessor }); } /** * Create a derived credential with selective disclosure */ async deriveCredential(originalCredential, options) { // 1. Validate original credential has BBS+ proof if (!originalCredential.proof || !this.isBbsProof(originalCredential.proof)) { throw new Error('Original credential must have BbsBlsSignature2020 proof'); } const originalProof = Array.isArray(originalCredential.proof) ? originalCredential.proof.find(p => p.type === 'BbsBlsSignature2020') : originalCredential.proof; // 2. Create frame for selective disclosure const frame = await this.createFrame(originalCredential, options.attributesToReveal); // 3. Frame the credential const framedCredential = await this.jsonLdProcessor.frame(originalCredential, frame); // 4. Extract revealed paths const revealedPaths = await this.extractRevealedPaths(originalCredential, framedCredential); // 5. Create derived proof const derivedProof = await this.suite.createDerivedProof({ document: originalCredential, proof: originalProof, revealedAttributes: revealedPaths, nonce: options.nonce }); // 6. Build derived credential const derivedCredential = { ...framedCredential, proof: derivedProof }; // 7. Add holder binding if requested if (options.holderDID) { derivedCredential.holder = options.holderDID; } return { derivedCredential, derivedProof, revealedPaths }; } /** * Verify a derived credential */ async verifyDerivedCredential(derivedCredential, issuerPublicKey, expectedNonce) { // 1. Check proof type if (!derivedCredential.proof || !this.isBbsDerivedProof(derivedCredential.proof)) { return false; } const derivedProof = Array.isArray(derivedCredential.proof) ? derivedCredential.proof.find(p => p.type === 'BbsBlsSignatureProof2020') : derivedCredential.proof; // 2. Check nonce if expected if (expectedNonce && derivedProof.nonce !== expectedNonce) { return false; } // 3. Verify the derived proof return await this.suite.verifyDerivedProof({ document: derivedCredential, proof: derivedProof, publicKey: issuerPublicKey, expectedPurpose: derivedProof.proofPurpose }); } /** * Create a JSON-LD frame for selective disclosure */ async createFrame(credential, attributesToReveal) { // Basic frame structure const frame = { '@context': credential['@context'], type: credential.type, credentialSubject: {} }; // Add required fields if (credential.issuer) frame.issuer = {}; if (credential.validFrom) frame.validFrom = {}; if (credential.validUntil) frame.validUntil = {}; // Build credentialSubject frame const subject = Array.isArray(credential.credentialSubject) ? credential.credentialSubject[0] : credential.credentialSubject; // Always include subject ID if present if (subject.id) { frame.credentialSubject['@id'] = {}; } // Add requested attributes for (const attr of attributesToReveal) { const path = attr.split('.'); let current = frame.credentialSubject; for (let i = 0; i < path.length - 1; i++) { if (!current[path[i]]) { current[path[i]] = {}; } current = current[path[i]]; } current[path[path.length - 1]] = {}; } return frame; } /** * Extract revealed paths from original and framed credentials */ async extractRevealedPaths(original, framed) { const paths = []; // Extract paths from credentialSubject const originalSubject = Array.isArray(original.credentialSubject) ? original.credentialSubject[0] : original.credentialSubject; const framedSubject = framed.credentialSubject; this.extractPathsRecursive(originalSubject, framedSubject, 'credentialSubject', paths); // Add other revealed top-level properties const topLevelProps = ['issuer', 'validFrom', 'validUntil', 'type', '@context']; for (const prop of topLevelProps) { if (framed[prop] !== undefined) { paths.push(prop); } } return paths; } /** * Recursively extract paths */ extractPathsRecursive(original, framed, currentPath, paths) { if (!framed || typeof framed !== 'object') { return; } for (const key of Object.keys(framed)) { if (key === '@id' || key === '@type') continue; const fullPath = `${currentPath}.${key}`; if (original[key] !== undefined) { paths.push(fullPath); if (typeof framed[key] === 'object' && !Array.isArray(framed[key])) { this.extractPathsRecursive(original[key], framed[key], fullPath, paths); } } } } /** * Check if proof is BBS+ signature */ isBbsProof(proof) { if (Array.isArray(proof)) { return proof.some(p => p.type === 'BbsBlsSignature2020'); } return proof.type === 'BbsBlsSignature2020'; } /** * Check if proof is BBS+ derived proof */ isBbsDerivedProof(proof) { if (Array.isArray(proof)) { return proof.some(p => p.type === 'BbsBlsSignatureProof2020'); } return proof.type === 'BbsBlsSignatureProof2020'; } /** * Estimate the privacy level of a selective disclosure * Returns a score from 0 (all revealed) to 1 (minimal revelation) */ async estimatePrivacyLevel(originalCredential, revealedPaths) { // Extract all possible paths from original const allPaths = await this.extractAllPaths(originalCredential); // Calculate privacy score const totalPaths = allPaths.length; const revealedCount = revealedPaths.length; if (totalPaths === 0) return 1; return 1 - (revealedCount / totalPaths); } /** * Extract all attribute paths from a credential */ async extractAllPaths(credential) { const paths = []; // Add top-level paths const topLevel = ['@context', 'type', 'issuer', 'validFrom', 'validUntil']; paths.push(...topLevel.filter(p => credential[p] !== undefined)); // Extract from credentialSubject const subject = Array.isArray(credential.credentialSubject) ? credential.credentialSubject[0] : credential.credentialSubject; this.extractAllPathsRecursive(subject, 'credentialSubject', paths); return paths; } /** * Recursively extract all paths */ extractAllPathsRecursive(obj, currentPath, paths) { if (!obj || typeof obj !== 'object') { return; } for (const key of Object.keys(obj)) { if (key === '@id' || key === '@type') continue; const fullPath = `${currentPath}.${key}`; paths.push(fullPath); if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { this.extractAllPathsRecursive(obj[key], fullPath, paths); } } } } exports.BbsSelectiveDisclosure = BbsSelectiveDisclosure; //# sourceMappingURL=bbs-selective-disclosure.js.map