UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

228 lines 15.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MasterCertificate = void 0; const Certificate_js_1 = __importDefault(require("./Certificate.js")); const Utils = __importStar(require("../../primitives/utils.js")); const SymmetricKey_js_1 = __importDefault(require("../../primitives/SymmetricKey.js")); const Random_js_1 = __importDefault(require("../../primitives/Random.js")); /** * MasterCertificate extends the base Certificate class to manage a master keyring, enabling the creation of verifiable certificates. * * It allows for the selective disclosure of certificate fields by creating a `VerifiableCertificate` for a specific verifier. * The `MasterCertificate` can securely decrypt each master key and re-encrypt it for a verifier, creating a customized * keyring containing only the keys necessary for the verifier to access designated fields. * */ class MasterCertificate extends Certificate_js_1.default { constructor(type, serialNumber, subject, certifier, revocationOutpoint, fields, masterKeyring, signature) { super(type, serialNumber, subject, certifier, revocationOutpoint, fields, signature); // Ensure every field in `fields` is a string and has a corresponding key in `masterKeyring` for (const fieldName of Object.keys(fields)) { if (masterKeyring[fieldName] === undefined || masterKeyring[fieldName] === '') { throw new Error(`Master keyring must contain a value for every field. Missing or empty key for field: "${fieldName}".`); } } this.masterKeyring = masterKeyring; } /** * Encrypts certificate fields for a subject and generates a master keyring. * This method returns a master keyring tied to a specific certifier or subject who will validate * and sign off on the fields, along with the encrypted certificate fields. * * @param {ProtoWallet} creatorWallet - The wallet of the creator responsible for encrypting the fields. * @param {WalletCounterparty} certifierOrSubject - The certifier or subject who will validate the certificate fields. * @param {Record<CertificateFieldNameUnder50Bytes, string>} fields - A record of certificate field names (under 50 bytes) mapped to their values. * @param {BooleanDefaultFalse} [privileged] - Whether this is a privileged request. * @param {DescriptionString5to50Bytes} [privilegedReason] - Reason provided for privileged access, required if this is a privileged operation. * * @returns {Promise<CreateCertificateFieldsResult>} A promise resolving to an object containing: * - `certificateFields` {Record<CertificateFieldNameUnder50Bytes, Base64String>}: * The encrypted certificate fields. * - `masterKeyring` {Record<CertificateFieldNameUnder50Bytes, Base64String>}: * The master keyring containing encrypted revelation keys for each field. */ static async createCertificateFields(creatorWallet, certifierOrSubject, fields, privileged, privilegedReason) { const certificateFields = {}; const masterKeyring = {}; for (const [fieldName, fieldValue] of Object.entries(fields)) { const fieldSymmetricKey = SymmetricKey_js_1.default.fromRandom(); const encryptedFieldValue = fieldSymmetricKey.encrypt(Utils.toArray(fieldValue, 'utf8')); certificateFields[fieldName] = Utils.toBase64(encryptedFieldValue); const { ciphertext: encryptedFieldRevelationKey } = await creatorWallet.encrypt({ plaintext: fieldSymmetricKey.toArray(), ...Certificate_js_1.default.getCertificateFieldEncryptionDetails(fieldName), counterparty: certifierOrSubject, privileged, privilegedReason }); masterKeyring[fieldName] = Utils.toBase64(encryptedFieldRevelationKey); } return { certificateFields, masterKeyring }; } /** * Creates a keyring for a verifier, enabling them to decrypt specific certificate fields. * This method decrypts the master field keys for the specified fields and re-encrypts them * for the verifier's identity key. The result is a keyring containing the keys necessary * for the verifier to access the designated fields. * * @param {ProtoWallet} subjectWallet - The wallet instance of the subject, used to decrypt and re-encrypt field keys. * @param {WalletCounterparty} verifier - The verifier who will receive access to the selectively revealed fields. Can be an identity key as hex, 'anyone', or 'self'. * @param {string[]} fieldsToReveal - An array of field names to be revealed to the verifier. Must be a subset of the certificate's fields. * @param {string} [originator] - Optional originator identifier, used if additional context is needed for decryption and encryption operations. * @returns {Promise<Record<CertificateFieldNameUnder50Bytes, string>>} - A keyring mapping field names to encrypted field revelation keys, allowing the verifier to decrypt specified fields. * @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: * - fieldsToReveal is not an array of strings. * - A field in `fieldsToReveal` does not exist in the certificate. * - The decrypted master field key fails to decrypt the corresponding field (indicating an invalid key). */ static async createKeyringForVerifier(subjectWallet, certifier, verifier, fields, fieldsToReveal, masterKeyring, serialNumber, privileged, privilegedReason) { if (!Array.isArray(fieldsToReveal)) { throw new Error('fieldsToReveal must be an array of strings'); } const fieldRevelationKeyring = {}; for (const fieldName of fieldsToReveal) { // Make sure that fields to reveal is a subset of the certificate fields if (fields[fieldName] === undefined || fields[fieldName] === null || fields[fieldName] === '') { throw new Error(`Fields to reveal must be a subset of the certificate fields. Missing the "${fieldName}" field.`); } // Decrypt the master field key and verify that derived key actually decrypts requested field const masterFieldKey = (await this.decryptField(subjectWallet, masterKeyring, fieldName, fields[fieldName], certifier, privileged, privilegedReason)).fieldRevelationKey; // Encrypt derived fieldRevelationKey for verifier const { ciphertext: encryptedFieldRevelationKey } = await subjectWallet.encrypt({ plaintext: masterFieldKey, ...Certificate_js_1.default.getCertificateFieldEncryptionDetails(fieldName, serialNumber), counterparty: verifier, privileged, privilegedReason }); // Add encryptedFieldRevelationKey to fieldRevelationKeyring fieldRevelationKeyring[fieldName] = Utils.toBase64(encryptedFieldRevelationKey); } // Return the field revelation keyring which can be used to create a verifiable certificate for a verifier. return fieldRevelationKeyring; } /** * Issues a new MasterCertificate for a specified subject. * * This method generates a certificate containing encrypted fields and a keyring * for the subject to decrypt all fields. Each field is encrypted with a randomly * generated symmetric key, which is then encrypted for the subject. The certificate * can also includes a revocation outpoint to manage potential revocation. * * @param {ProtoWallet} certifierWallet - The wallet of the certifier, used to sign the certificate and encrypt field keys. * @param {WalletCounterparty} subject - The subject for whom the certificate is issued. * @param {Record<CertificateFieldNameUnder50Bytes, string>} fields - Unencrypted certificate fields to include, with their names and values. * @param {string} certificateType - The type of certificate being issued. * @param {function(string, Record<CertificateFieldNameUnder50Bytes, string>?): Promise<string>} getRevocationOutpoint - * Optional function to obtain a revocation outpoint for the certificate. Defaults to a placeholder. * @param {function(string): Promise<void>} updateProgress - Optional callback for reporting progress updates during the operation. Defaults to a no-op. * @returns {Promise<MasterCertificate>} - A signed MasterCertificate instance containing the encrypted fields and subject specific keyring. * * @throws {Error} Throws an error if any operation (e.g., encryption, signing) fails during certificate issuance. */ static async issueCertificateForSubject(certifierWallet, subject, fields, certificateType, getRevocationOutpoint = async (_serial) => { void _serial; // Explicitly acknowledge unused parameter return 'Certificate revocation not tracked.'; }, serialNumber) { // 1. Generate a random serialNumber if not provided const finalSerialNumber = serialNumber ?? Utils.toBase64((0, Random_js_1.default)(32)); // 2. Create encrypted certificate fields and associated master keyring const { certificateFields, masterKeyring } = await this.createCertificateFields(certifierWallet, subject, fields); // 3. Obtain a revocation outpoint const revocationOutpoint = await getRevocationOutpoint(finalSerialNumber); // 4. Create new MasterCertificate instance const certificate = new MasterCertificate(certificateType, finalSerialNumber, subject, (await certifierWallet.getPublicKey({ identityKey: true })).publicKey, revocationOutpoint, certificateFields, masterKeyring); // 5. Sign and return the new MasterCertificate certifying the subject. await certificate.sign(certifierWallet); return certificate; } /** * Decrypts all fields in the MasterCertificate using the subject's or certifier's wallet. * * This method allows the subject or certifier to decrypt the `masterKeyring` and retrieve * the encryption keys for each field, which are then used to decrypt the corresponding field values. * The counterparty used for decryption depends on how the certificate fields were created: * - If the certificate is self-signed, the counterparty should be set to 'self'. * - Otherwise, the counterparty should always be the other party involved in the certificate issuance process (the subject or certifier). * * @param {ProtoWallet} subjectOrCertifierWallet - The wallet of the subject or certifier, used to decrypt the master keyring and field values. * @param {Record<CertificateFieldNameUnder50Bytes, Base64String>} masterKeyring - A record containing encrypted keys for each field. * @param {Record<CertificateFieldNameUnder50Bytes, Base64String>} fields - A record of encrypted field names and their values. * @param {WalletCounterparty} counterparty - The counterparty responsible for creating or signing the certificate. For self-signed certificates, use 'self'. * @param {BooleanDefaultFalse} [privileged] - Whether this is a privileged request. * @param {DescriptionString5to50Bytes} [privilegedReason] - Reason provided for privileged access, required if this is a privileged operation. * @returns {Promise<Record<CertificateFieldNameUnder50Bytes, string>>} A promise resolving to a record of field names and their decrypted values in plaintext. * * @throws {Error} Throws an error if the `masterKeyring` is invalid or if decryption fails for any field. */ static async decryptFields(subjectOrCertifierWallet, masterKeyring, fields, counterparty, privileged, privilegedReason) { if (masterKeyring == null || Object.keys(masterKeyring).length === 0) { throw new Error('A MasterCertificate must have a valid masterKeyring!'); } try { const decryptedFields = {}; // Note: we want to iterate through all fields, not just masterKeyring keys/value pairs. for (const fieldName of Object.keys(fields)) { decryptedFields[fieldName] = (await this.decryptField(subjectOrCertifierWallet, masterKeyring, fieldName, fields[fieldName], counterparty, privileged, privilegedReason)).decryptedFieldValue; } return decryptedFields; } catch { throw new Error('Failed to decrypt all master certificate fields.'); } } static async decryptField(subjectOrCertifierWallet, masterKeyring, fieldName, fieldValue, counterparty, privileged, privilegedReason) { if (masterKeyring == null || Object.keys(masterKeyring).length === 0) { throw new Error('A MasterCertificate must have a valid masterKeyring!'); } try { const { plaintext: fieldRevelationKey } = await subjectOrCertifierWallet.decrypt({ ciphertext: Utils.toArray(masterKeyring[fieldName], 'base64'), ...Certificate_js_1.default.getCertificateFieldEncryptionDetails(fieldName), counterparty, privileged, privilegedReason }); const decryptedFieldValue = new SymmetricKey_js_1.default(fieldRevelationKey).decrypt(Utils.toArray(fieldValue, 'base64')); return { fieldRevelationKey, decryptedFieldValue: Utils.toUTF8(decryptedFieldValue) }; } catch { throw new Error('Failed to decrypt certificate field!'); } } } exports.MasterCertificate = MasterCertificate; //# sourceMappingURL=MasterCertificate.js.map