UNPKG

@dwn-protocol/id-sdk

Version:

SDK for accessing the features and capabilities

266 lines 12.1 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { VerifiableCredential as VC } from './credential.js'; import { v4 as uuidv4 } from 'uuid'; import { Convert } from '../common/index.js'; import pako from 'pako'; /** * Constants for Status List 2021 implementation */ export const STATUS_LIST_DATA_FORMAT = 'application/vc-status-list+jwt'; export const STATUS_LIST_SCHEMA = 'StatusList2021Credential'; export const STATUS_LIST_CONTEXT = 'https://w3id.org/vc/status-list/2021/v1'; /** * Status List 2021 implementation for credential revocation * Based on: https://w3c.github.io/vc-status-list-2021/ * * @beta */ export class StatusListManager { constructor(options) { this.agent = options.agent; this.connectedDid = options.connectedDid; this.dwnApi = options.dwnApi; } /** * Create a Status List Credential for tracking revocation/suspension status * * @param options - Options for creating the status list * @returns Status list credential and DWN record */ createStatusList(options) { return __awaiter(this, void 0, void 0, function* () { const { issuer, statusPurpose, signOptions, size = 131072 } = options; // Create empty bitstring (all zeros = no credentials revoked) const bitstring = new Uint8Array(Math.ceil(size / 8)); bitstring.fill(0); // Compress bitstring with gzip const compressed = pako.gzip(bitstring); // Base64url encode the compressed bitstring const encodedList = Convert.uint8Array(compressed).toBase64Url(); // Create status list credential subject const statusListId = `urn:uuid:${uuidv4()}`; const credentialSubject = { id: `${statusListId}#list`, type: 'StatusList2021', statusPurpose: statusPurpose, encodedList: encodedList }; // Create the status list credential // Note: The subject for a status list credential is the list itself // The subject parameter will be overridden by the id in credentialSubject data const statusListCredential = VC.create({ issuer: issuer, subject: statusListId, data: credentialSubject, type: 'StatusList2021Credential' }); // Add Status List context statusListCredential.vcDataModel['@context'] = [ 'https://www.w3.org/2018/credentials/v1', STATUS_LIST_CONTEXT ]; // Update type to include StatusList2021Credential statusListCredential.vcDataModel.type = [ 'VerifiableCredential', 'StatusList2021Credential' ]; // Set the ID statusListCredential.vcDataModel.id = statusListId; // Sign the status list credential const statusListJwt = yield statusListCredential.sign(signOptions); // Store in DWN const { record } = yield this.dwnApi.records.create({ data: statusListJwt, message: { schema: STATUS_LIST_SCHEMA, dataFormat: STATUS_LIST_DATA_FORMAT, }, }); return { statusListCredential, statusListJwt, record }; }); } /** * Add credentialStatus to a credential, linking it to a status list * * @param vc - The verifiable credential to add status to * @param statusListCredentialId - The ID of the status list credential * @param statusListIndex - The index in the status list for this credential * @param statusPurpose - The purpose (revocation or suspension) * @returns The credential with credentialStatus added */ addCredentialStatus(vc, statusListCredentialId, statusListIndex, statusPurpose = 'revocation') { const credentialStatus = { id: `${statusListCredentialId}#${statusListIndex}`, type: 'StatusList2021Entry', statusPurpose: statusPurpose, statusListIndex: statusListIndex.toString(), statusListCredential: statusListCredentialId }; // Add credentialStatus to the VC vc.vcDataModel.credentialStatus = credentialStatus; // Add Status List context if not already present const contexts = Array.isArray(vc.vcDataModel['@context']) ? vc.vcDataModel['@context'] : [vc.vcDataModel['@context']]; if (!contexts.includes(STATUS_LIST_CONTEXT)) { vc.vcDataModel['@context'] = [...contexts, STATUS_LIST_CONTEXT]; } return vc; } /** * Revoke a credential by updating the status list bitstring * * @param options - Options for revoking the credential * @returns Updated status list credential and record */ revokeCredential(options) { return __awaiter(this, void 0, void 0, function* () { return this.updateCredentialStatus(Object.assign(Object.assign({}, options), { revoked: true })); }); } /** * Suspend a credential by updating the status list bitstring * * @param options - Options for suspending the credential * @returns Updated status list credential and record */ suspendCredential(options) { return __awaiter(this, void 0, void 0, function* () { return this.updateCredentialStatus(Object.assign(Object.assign({}, options), { revoked: true // Suspension also sets the bit to 1 })); }); } /** * Check if a credential is revoked or suspended * * @param options - Options for checking status * @returns Status information */ checkStatus(options) { return __awaiter(this, void 0, void 0, function* () { const { statusListCredentialId, statusListIndex, statusListRecordId } = options; let statusListJwt; // Resolve status list credential if (statusListRecordId) { // Read from DWN const { record } = yield this.dwnApi.records.read({ message: { filter: { recordId: statusListRecordId } } }); statusListJwt = yield record.data.text(); } else { // Try to resolve from URL (external status list) // For now, we'll require statusListRecordId for SDK-native status lists throw new Error('statusListRecordId is required for DWN-based status lists'); } // Parse the status list credential const statusListVc = VC.parseJwt(statusListJwt); const credentialSubject = statusListVc.vcDataModel.credentialSubject; if (!credentialSubject || !credentialSubject.encodedList) { throw new Error('Invalid status list credential: missing encodedList'); } // Decode and decompress the bitstring const encodedList = credentialSubject.encodedList; const compressed = Convert.base64Url(encodedList).toUint8Array(); const bitstring = pako.ungzip(compressed); // Check the bit at the specified index const byteIndex = Math.floor(statusListIndex / 8); const bitIndex = statusListIndex % 8; if (byteIndex >= bitstring.length) { throw new Error(`Status list index ${statusListIndex} is out of bounds`); } const byte = bitstring[byteIndex]; const bit = (byte >> bitIndex) & 1; const isRevoked = bit === 1; // Determine if it's revocation or suspension based on statusPurpose const statusPurpose = credentialSubject.statusPurpose; const revoked = isRevoked && statusPurpose === 'revocation'; const suspended = isRevoked && statusPurpose === 'suspension'; return { revoked, suspended }; }); } /** * Private method to update credential status in a status list */ updateCredentialStatus(options) { return __awaiter(this, void 0, void 0, function* () { const { statusListRecordId, statusListIndex, signOptions, revoked } = options; // Read the existing status list record const { record } = yield this.dwnApi.records.read({ message: { filter: { recordId: statusListRecordId } } }); const statusListJwt = yield record.data.text(); const statusListVc = VC.parseJwt(statusListJwt); // Get the credential subject const credentialSubject = statusListVc.vcDataModel.credentialSubject; if (!credentialSubject || !credentialSubject.encodedList) { throw new Error('Invalid status list credential: missing encodedList'); } // Decode and decompress the bitstring const encodedList = credentialSubject.encodedList; const compressed = Convert.base64Url(encodedList).toUint8Array(); const bitstring = pako.ungzip(compressed); // Update the bit at the specified index const byteIndex = Math.floor(statusListIndex / 8); const bitIndex = statusListIndex % 8; if (byteIndex >= bitstring.length) { throw new Error(`Status list index ${statusListIndex} is out of bounds`); } // Set or clear the bit if (revoked) { bitstring[byteIndex] |= (1 << bitIndex); // Set bit to 1 } else { bitstring[byteIndex] &= ~(1 << bitIndex); // Clear bit to 0 } // Re-compress and encode const newCompressed = pako.gzip(bitstring); const newEncodedList = Convert.uint8Array(newCompressed).toBase64Url(); // Update the credential subject credentialSubject.encodedList = newEncodedList; // Create updated status list credential const updatedStatusListVc = VC.create({ issuer: statusListVc.issuer, subject: statusListVc.vcDataModel.id || `urn:uuid:${uuidv4()}`, data: credentialSubject, type: 'StatusList2021Credential' }); // Preserve context and type updatedStatusListVc.vcDataModel['@context'] = statusListVc.vcDataModel['@context']; updatedStatusListVc.vcDataModel.type = statusListVc.vcDataModel.type; updatedStatusListVc.vcDataModel.id = statusListVc.vcDataModel.id; // Sign the updated credential const updatedStatusListJwt = yield updatedStatusListVc.sign(signOptions); // Update the DWN record yield record.update({ data: updatedStatusListJwt }); return { statusListCredential: updatedStatusListVc, statusListJwt: updatedStatusListJwt, record }; }); } } //# sourceMappingURL=status-list.js.map