UNPKG

wallet-storage-client

Version:
180 lines 9.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CertOps = void 0; const sdk_1 = require("@bsv/sdk"); const index_client_1 = require("../index.client"); const sdk_2 = require("@bsv/sdk"); const WERR_errors_1 = require("./WERR_errors"); class CertOps extends sdk_1.Certificate { constructor(wallet, wc) { super(wc.type, wc.serialNumber, wc.subject, wc.certifier, wc.revocationOutpoint, wc.fields, wc.signature); this.wallet = wallet; } static async fromCounterparty(wallet, e) { const c = new CertOps(wallet, e.certificate); // confirm cert verifies and decrypts. await c.verify(); await c.decryptFields(e.counterparty, e.keyring); // un-decrypt c.fields = c._encryptedFields; return c; } static async fromCertifier(wallet, e) { return await CertOps.fromCounterparty(wallet, { counterparty: e.certificate.certifier, ...e }); } static async fromEncrypted(wallet, wc, keyring) { const c = new CertOps(wallet, wc); c._keyring = keyring; c._encryptedFields = this.copyFields(c.fields); c._decryptedFields = await c.decryptFields(); await c.verify(); return c; } static async fromDecrypted(wallet, wc) { const c = new CertOps(wallet, wc); ({ fields: c._encryptedFields, keyring: c._keyring } = await c.encryptFields()); c._decryptedFields = await c.decryptFields(); return c; } static copyFields(fields) { const copy = {}; for (const [n, v] of Object.entries(fields)) copy[n] = v; return copy; } static getProtocolForCertificateFieldEncryption(serialNumber, fieldName) { return { protocolID: [2, 'certificate field encryption'], keyID: `${serialNumber} ${fieldName}` }; } exportForSubject() { if (!this._keyring || !this._encryptedFields || !this.signature || this.signature.length === 0) throw new WERR_errors_1.WERR_INVALID_OPERATION(`Certificate must be encrypted and signed prior to export.`); const certificate = this.toWalletCertificate(); const keyring = this._keyring; return { certificate, keyring }; } toWalletCertificate() { const wc = { signature: '', ...this }; return wc; } async encryptFields(counterparty = 'self') { const fields = this._decryptedFields || this.fields; const encryptedFields = {}; const keyring = {}; for (const fieldName of Object.keys(fields)) { const fieldSymmetricKey = sdk_2.SymmetricKey.fromRandom(); const encryptedFieldValue = fieldSymmetricKey.encrypt(sdk_2.Utils.toArray(this.fields[fieldName], 'utf8')); encryptedFields[fieldName] = sdk_2.Utils.toBase64(encryptedFieldValue); const encryptedFieldKey = await this.wallet.encrypt({ plaintext: fieldSymmetricKey.toArray(), counterparty, ...CertOps.getProtocolForCertificateFieldEncryption(this.serialNumber, fieldName) }); keyring[fieldName] = sdk_2.Utils.toBase64(encryptedFieldKey.ciphertext); } this._keyring = keyring; this._decryptedFields = fields; this.fields = this._encryptedFields = encryptedFields; return { fields: encryptedFields, keyring }; } async decryptFields(counterparty, keyring) { keyring || (keyring = this._keyring); const fields = this._encryptedFields || this.fields; const decryptedFields = {}; if (!keyring) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('keyring', 'valid'); try { for (const fieldName of Object.keys(keyring)) { const { plaintext: fieldRevelationKey } = await this.wallet.decrypt({ ciphertext: sdk_2.Utils.toArray(keyring[fieldName], 'base64'), counterparty: counterparty || this.subject, ...CertOps.getProtocolForCertificateFieldEncryption(this.serialNumber, fieldName) }); const fieldValue = new sdk_2.SymmetricKey(fieldRevelationKey).decrypt(sdk_2.Utils.toArray(fields[fieldName], 'base64')); decryptedFields[fieldName] = sdk_2.Utils.toUTF8(fieldValue); } this._keyring = keyring; this._encryptedFields = fields; this.fields = this._decryptedFields = decryptedFields; return decryptedFields; } catch (eu) { const e = index_client_1.sdk.WalletError.fromUnknown(eu); throw e; } } async exportForCounterparty( /** The incoming counterparty is who they are to us. */ counterparty, fieldsToReveal) { if (!this._keyring || !this._encryptedFields || !this.signature || this.signature.length === 0) throw new WERR_errors_1.WERR_INVALID_OPERATION(`Certificate must be encrypted and signed prior to export.`); const certificate = this.toWalletCertificate(); const keyring = await this.createKeyringForVerifier(counterparty, fieldsToReveal); // The exported counterparty is who we are to them... return { certificate, keyring, counterparty: await (0, index_client_1.getIdentityKey)(this.wallet) }; } /** * Creates a verifiable certificate structure for a specific verifier, allowing them access to specified fields. * This method decrypts the master field keys for each field specified in `fieldsToReveal` and re-encrypts them * for the verifier's identity key. The resulting certificate structure includes only the fields intended to be * revealed and a verifier-specific keyring for field decryption. * * @param {PubKeyHex} verifierIdentityKey - The public identity key of the verifier who will receive access to the specified fields. * @param {CertificateFieldNameUnder50Bytes[]} fieldsToReveal - An array of field names to be revealed to the verifier. Must be a subset of the certificate's fields. * @returns {Promise<Record<CertificateFieldNameUnder50Bytes[], Base64String>} - A new certificate structure containing the original encrypted fields, the verifier-specific field decryption keyring, and essential certificate metadata. * @throws {WERR_INVALID_PARAMETER} Throws an error if: * - fieldsToReveal is empty or 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). */ async createKeyringForVerifier(verifierIdentityKey, fieldsToReveal) { if (!this._keyring || !this._encryptedFields) throw new index_client_1.sdk.WERR_INVALID_OPERATION(`certificate must be encrypted`); if (!Array.isArray(fieldsToReveal) || fieldsToReveal.some(n => this._encryptedFields[n] === undefined)) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('fieldsToReveal', `an array of certificate field names`); const fieldRevelationKeyring = {}; for (const fieldName of fieldsToReveal) { // Create a keyID const encryptedFieldKey = this._keyring[fieldName]; const protocol = CertOps.getProtocolForCertificateFieldEncryption(this.serialNumber, fieldName); // Decrypt the master field key const { plaintext: fieldKey } = await this.wallet.decrypt({ ciphertext: sdk_2.Utils.toArray(encryptedFieldKey, 'base64'), counterparty: this.certifier, ...protocol }); // Verify that derived key actually decrypts requested field try { new sdk_2.SymmetricKey(fieldKey).decrypt(sdk_2.Utils.toArray(this.fields[fieldName], 'base64')); } catch (_) { throw new index_client_1.sdk.WERR_INTERNAL(`unable to decrypt field "${fieldName}" using derived field key.`); } // Encrypt derived fieldRevelationKey for verifier const { ciphertext: encryptedFieldRevelationKey } = await this.wallet.encrypt({ plaintext: fieldKey, counterparty: verifierIdentityKey, ...protocol }); // Add encryptedFieldRevelationKey to fieldRevelationKeyring fieldRevelationKeyring[fieldName] = sdk_2.Utils.toBase64(encryptedFieldRevelationKey); } // Return the field revelation keyring which can be used to create a verifiable certificate for a verifier. return fieldRevelationKeyring; } /** * encrypt plaintext field values for the subject * update the signature using the certifier's private key. */ async encryptAndSignNewCertificate() { if (await (0, index_client_1.getIdentityKey)(this.wallet) !== this.certifier) throw new index_client_1.sdk.WERR_INVALID_PARAMETER('wallet', 'the certifier for new certificate issuance.'); await this.encryptFields(this.subject); await this.sign(this.wallet); // Confirm the signed certificate verifies: await this.verify(); } } exports.CertOps = CertOps; //# sourceMappingURL=CertOps.js.map