wallet-storage-client
Version:
Client only Wallet Storage
180 lines • 9.33 kB
JavaScript
"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