anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
221 lines • 7.75 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompositeStatusChecker = exports.StatusList2021StatusChecker = exports.StatusList2021 = exports.RevocationList2020StatusChecker = void 0;
const vc2_1 = require("../types/vc2");
const jose_1 = require("jose");
/**
* RevocationList2020 status checker
* Compatible with existing revocation implementation
*/
class RevocationList2020StatusChecker {
constructor(revocationLists = new Map()) {
this.revocationLists = revocationLists;
}
async checkStatus(credentialId, statusInfo) {
if (statusInfo.type !== vc2_1.CredentialStatusType.REVOCATION_LIST_2020) {
throw new Error(`Unsupported status type: ${statusInfo.type}`);
}
const list = this.revocationLists.get(statusInfo.id);
if (!list) {
// If we can't find the list, assume not revoked
return {
revoked: false,
checkedAt: new Date().toISOString()
};
}
const isRevoked = list.revokedCredentials?.includes(credentialId) || false;
return {
revoked: isRevoked,
reason: isRevoked ? 'Credential has been revoked' : undefined,
checkedAt: new Date().toISOString()
};
}
/**
* Add or update a revocation list
*/
addRevocationList(listId, list) {
this.revocationLists.set(listId, list);
}
}
exports.RevocationList2020StatusChecker = RevocationList2020StatusChecker;
/**
* StatusList2021 implementation
* Uses a bitstring to efficiently store credential status
*/
class StatusList2021 {
constructor(size = 100000) {
this.size = size;
// Each byte holds 8 bits
this.bitstring = new Uint8Array(Math.ceil(size / 8));
}
/**
* Set status for a credential at given index
* @param index The index in the status list
* @param revoked Whether the credential is revoked
*/
setStatus(index, revoked) {
if (index < 0 || index >= this.size) {
throw new Error(`Index ${index} out of bounds (0-${this.size - 1})`);
}
const byteIndex = Math.floor(index / 8);
const bitIndex = index % 8;
if (revoked) {
// Set bit to 1
this.bitstring[byteIndex] |= (1 << bitIndex);
}
else {
// Set bit to 0
this.bitstring[byteIndex] &= ~(1 << bitIndex);
}
}
/**
* Check status for a credential at given index
* @param index The index in the status list
* @returns Whether the credential is revoked
*/
getStatus(index) {
if (index < 0 || index >= this.size) {
throw new Error(`Index ${index} out of bounds (0-${this.size - 1})`);
}
const byteIndex = Math.floor(index / 8);
const bitIndex = index % 8;
return (this.bitstring[byteIndex] & (1 << bitIndex)) !== 0;
}
/**
* Encode the bitstring as base64
*/
encode() {
return Buffer.from(this.bitstring).toString('base64');
}
/**
* Decode from base64
*/
static decode(encoded, size) {
const list = new StatusList2021(size);
list.bitstring = new Uint8Array(Buffer.from(encoded, 'base64'));
return list;
}
/**
* Create a signed status list credential
*/
async createStatusListCredential(issuerDID, privateKey, listId) {
const now = new Date().toISOString();
const statusListCredential = {
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://w3id.org/vc/status-list/2021/v1"
],
id: listId,
type: ["VerifiableCredential", "StatusList2021Credential"],
issuer: issuerDID.id,
validFrom: now,
credentialSubject: {
id: `${listId}#list`,
type: "StatusList2021",
statusPurpose: "revocation",
encodedList: this.encode()
}
};
// Sign the credential
const jwt = await new jose_1.SignJWT({
vc: statusListCredential,
iss: issuerDID.id,
sub: statusListCredential.credentialSubject.id
})
.setProtectedHeader({ alg: 'EdDSA', typ: 'JWT' })
.setIssuedAt()
.sign(privateKey);
return {
...statusListCredential,
proof: {
type: "Ed25519Signature2020",
created: now,
verificationMethod: `${issuerDID.id}#key-1`,
proofPurpose: "assertionMethod",
jws: jwt
}
};
}
}
exports.StatusList2021 = StatusList2021;
/**
* StatusList2021 status checker
*/
class StatusList2021StatusChecker {
constructor(statusLists = new Map()) {
this.statusLists = statusLists;
}
async checkStatus(credentialId, statusInfo) {
if (statusInfo.type !== vc2_1.CredentialStatusType.STATUS_LIST_2021) {
throw new Error(`Unsupported status type: ${statusInfo.type}`);
}
const statusListIndex = statusInfo.statusListIndex;
if (typeof statusListIndex !== 'number') {
throw new Error('StatusList2021 requires statusListIndex');
}
const list = this.statusLists.get(statusInfo.id);
if (!list) {
// If we can't find the list, assume not revoked
return {
revoked: false,
statusListIndex,
checkedAt: new Date().toISOString()
};
}
const isRevoked = list.getStatus(statusListIndex);
return {
revoked: isRevoked,
statusListIndex,
reason: isRevoked ? 'Credential has been revoked' : undefined,
checkedAt: new Date().toISOString()
};
}
/**
* Add or update a status list
*/
addStatusList(listId, list) {
this.statusLists.set(listId, list);
}
/**
* Load a status list from a credential
*/
async loadStatusListCredential(credential) {
// Verify the credential first
if (!credential.proof?.jws) {
throw new Error('Status list credential must be signed');
}
// Extract the encoded list
const encodedList = credential.credentialSubject?.encodedList;
if (!encodedList) {
throw new Error('Status list credential missing encodedList');
}
// TODO: Get size from credential metadata
const size = 100000; // Default size
const list = StatusList2021.decode(encodedList, size);
this.addStatusList(credential.id, list);
}
}
exports.StatusList2021StatusChecker = StatusList2021StatusChecker;
/**
* Composite status checker that supports multiple status types
*/
class CompositeStatusChecker {
constructor() {
this.checkers = new Map();
// Register default checkers
this.registerChecker(vc2_1.CredentialStatusType.REVOCATION_LIST_2020, new RevocationList2020StatusChecker());
this.registerChecker(vc2_1.CredentialStatusType.STATUS_LIST_2021, new StatusList2021StatusChecker());
}
registerChecker(type, checker) {
this.checkers.set(type, checker);
}
async checkStatus(credentialId, statusInfo) {
const checker = this.checkers.get(statusInfo.type);
if (!checker) {
throw new Error(`No status checker registered for type: ${statusInfo.type}`);
}
return checker.checkStatus(credentialId, statusInfo);
}
}
exports.CompositeStatusChecker = CompositeStatusChecker;
//# sourceMappingURL=credential-status.js.map