UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

125 lines (116 loc) 4.7 kB
import type { PubKeyHex, Base64String, CertificateFieldNameUnder50Bytes, HexString, OutpointString, WalletCertificate, } from '../../wallet/Wallet.interfaces.js' import SymmetricKey from '../../primitives/SymmetricKey.js' import * as Utils from '../../primitives/utils.js' import ProtoWallet from '../../wallet/ProtoWallet.js' import Certificate from './Certificate.js' /** * VerifiableCertificate extends the Certificate class, adding functionality to manage a verifier-specific keyring. * This keyring allows selective decryption of certificate fields for authorized verifiers. */ export class VerifiableCertificate extends Certificate { declare type: Base64String declare serialNumber: Base64String declare subject: PubKeyHex declare certifier: PubKeyHex declare revocationOutpoint: OutpointString declare fields: Record<CertificateFieldNameUnder50Bytes, string> declare signature?: HexString keyring: Record<CertificateFieldNameUnder50Bytes, string> decryptedFields?: Record<CertificateFieldNameUnder50Bytes, Base64String> constructor( type: Base64String, serialNumber: Base64String, subject: PubKeyHex, certifier: PubKeyHex, revocationOutpoint: OutpointString, fields: Record<CertificateFieldNameUnder50Bytes, string>, keyring: Record<CertificateFieldNameUnder50Bytes, string>, signature?: HexString, decryptedFields?: Record<CertificateFieldNameUnder50Bytes, Base64String> ) { super( type, serialNumber, subject, certifier, revocationOutpoint, fields, signature ) this.keyring = keyring this.decryptedFields = decryptedFields } /** * * @param {WalletCertificate} certificate – The source certificate that was issued and signed by the certifier. * @param {Record<CertificateFieldNameUnder50Bytes, string>} keyring – A allows the verifier to decrypt selected certificate fields. * @returns {VerifiableCertificate} – A fully-formed instance containing the * original certificate data plus the supplied keyring. */ static fromCertificate( certificate: WalletCertificate, keyring: Record<CertificateFieldNameUnder50Bytes, string> ): VerifiableCertificate { return new VerifiableCertificate( certificate.type, certificate.serialNumber, certificate.subject, certificate.certifier, certificate.revocationOutpoint, certificate.fields, keyring, certificate.signature ) } /** * Decrypts selectively revealed certificate fields using the provided keyring and verifier wallet * @param {ProtoWallet} verifierWallet - The wallet instance of the certificate's verifier, used to decrypt field keys. * @returns {Promise<Record<CertificateFieldNameUnder50Bytes, string>>} - A promise that resolves to an object where each key is a field name and each value is the decrypted field value as a string. * @param {BooleanDefaultFalse} [privileged] - Whether this is a privileged request. * @param {DescriptionString5to50Bytes} [privilegedReason] - Reason provided for privileged access, required if this is a privileged operation. * @throws {Error} Throws an error if any of the decryption operations fail, with a message indicating the failure context. */ async decryptFields( verifierWallet: ProtoWallet, privileged?: boolean, privilegedReason?: string ): Promise<Record<CertificateFieldNameUnder50Bytes, string>> { if (this.keyring == null || Object.keys(this.keyring).length === 0) { // ✅ Explicitly check null and empty object throw new Error( 'A keyring is required to decrypt certificate fields for the verifier.' ) } try { const decryptedFields: Record<CertificateFieldNameUnder50Bytes, string> = {} for (const fieldName in this.keyring) { const { plaintext: fieldRevelationKey } = await verifierWallet.decrypt({ ciphertext: Utils.toArray(this.keyring[fieldName], 'base64'), ...Certificate.getCertificateFieldEncryptionDetails( fieldName, this.serialNumber ), counterparty: this.subject, privileged, privilegedReason }) const fieldValue = new SymmetricKey(fieldRevelationKey).decrypt( Utils.toArray(this.fields[fieldName], 'base64') ) decryptedFields[fieldName] = Utils.toUTF8(fieldValue as number[]) } return decryptedFields } catch (error) { throw new Error( `Failed to decrypt selectively revealed certificate fields using keyring: ${String(error instanceof Error ? error.message : error)}` ) } } }