UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

216 lines 10.6 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 }); const Utils = __importStar(require("../../primitives/utils.js")); const ProtoWallet_js_1 = __importDefault(require("../../wallet/ProtoWallet.js")); const Signature_js_1 = __importDefault(require("../../primitives/Signature.js")); /** * Represents an Identity Certificate as per the Wallet interface specifications. * * This class provides methods to serialize and deserialize certificates, as well as signing and verifying the certificate's signature. */ class Certificate { /** * Constructs a new Certificate. * * @param {Base64String} type - Type identifier for the certificate, base64 encoded string, 32 bytes. * @param {Base64String} serialNumber - Unique serial number of the certificate, base64 encoded string, 32 bytes. * @param {PubKeyHex} subject - The public key belonging to the certificate's subject, compressed public key hex string. * @param {PubKeyHex} certifier - Public key of the certifier who issued the certificate, compressed public key hex string. * @param {OutpointString} revocationOutpoint - The outpoint used to confirm that the certificate has not been revoked (TXID.OutputIndex), as a string. * @param {Record<CertificateFieldNameUnder50Bytes, string>} fields - All the fields present in the certificate. * @param {HexString} signature - Certificate signature by the certifier's private key, DER encoded hex string. */ constructor(type, serialNumber, subject, certifier, revocationOutpoint, fields, signature) { this.type = type; this.serialNumber = serialNumber; this.subject = subject; this.certifier = certifier; this.revocationOutpoint = revocationOutpoint; this.fields = fields; this.signature = signature; } /** * Serializes the certificate into binary format, with or without a signature. * * @param {boolean} [includeSignature=true] - Whether to include the signature in the serialization. * @returns {number[]} - The serialized certificate in binary format. */ toBinary(includeSignature = true) { const writer = new Utils.Writer(); // Write type (Base64String, 32 bytes) const typeBytes = Utils.toArray(this.type, 'base64'); writer.write(typeBytes); // Write serialNumber (Base64String, 32 bytes) const serialNumberBytes = Utils.toArray(this.serialNumber, 'base64'); writer.write(serialNumberBytes); // Write subject (33 bytes compressed PubKeyHex) const subjectBytes = Utils.toArray(this.subject, 'hex'); writer.write(subjectBytes); // Write certifier (33 bytes compressed PubKeyHex) const certifierBytes = Utils.toArray(this.certifier, 'hex'); writer.write(certifierBytes); // Write revocationOutpoint (TXID + OutputIndex) const [txid, outputIndex] = this.revocationOutpoint.split('.'); const txidBytes = Utils.toArray(txid, 'hex'); writer.write(txidBytes); writer.writeVarIntNum(Number(outputIndex)); // Write fields // Sort field names lexicographically const fieldNames = Object.keys(this.fields).sort(); writer.writeVarIntNum(fieldNames.length); for (const fieldName of fieldNames) { const fieldValue = this.fields[fieldName]; // Field name const fieldNameBytes = Utils.toArray(fieldName, 'utf8'); writer.writeVarIntNum(fieldNameBytes.length); writer.write(fieldNameBytes); // Field value const fieldValueBytes = Utils.toArray(fieldValue, 'utf8'); writer.writeVarIntNum(fieldValueBytes.length); writer.write(fieldValueBytes); } // Write signature if included if (includeSignature && (this.signature ?? '').length > 0) { // ✅ Explicitly handle nullish signature const signatureBytes = Utils.toArray(this.signature, 'hex'); // ✅ Type assertion ensures it's a string writer.write(signatureBytes); } return writer.toArray(); } /** * Deserializes a certificate from binary format. * * @param {number[]} bin - The binary data representing the certificate. * @returns {Certificate} - The deserialized Certificate object. */ static fromBinary(bin) { const reader = new Utils.Reader(bin); // Read type const typeBytes = reader.read(32); const type = Utils.toBase64(typeBytes); // Read serialNumber const serialNumberBytes = reader.read(32); const serialNumber = Utils.toBase64(serialNumberBytes); // Read subject (33 bytes) const subjectBytes = reader.read(33); const subject = Utils.toHex(subjectBytes); // Read certifier (33 bytes) const certifierBytes = reader.read(33); const certifier = Utils.toHex(certifierBytes); // Read revocationOutpoint const txidBytes = reader.read(32); const txid = Utils.toHex(txidBytes); const outputIndex = reader.readVarIntNum(); const revocationOutpoint = `${txid}.${outputIndex}`; // Read fields const numFields = reader.readVarIntNum(); const fields = {}; for (let i = 0; i < numFields; i++) { // Field name const fieldNameLength = reader.readVarIntNum(); const fieldNameBytes = reader.read(fieldNameLength); const fieldName = Utils.toUTF8(fieldNameBytes); // Field value const fieldValueLength = reader.readVarIntNum(); const fieldValueBytes = reader.read(fieldValueLength); const fieldValue = Utils.toUTF8(fieldValueBytes); fields[fieldName] = fieldValue; } // Read signature if present let signature; if (!reader.eof()) { const signatureBytes = reader.read(); const sig = Signature_js_1.default.fromDER(signatureBytes); signature = sig.toString('hex'); } return new Certificate(type, serialNumber, subject, certifier, revocationOutpoint, fields, signature); } /** * Verifies the certificate's signature. * * @returns {Promise<boolean>} - A promise that resolves to true if the signature is valid. */ async verify() { // A verifier can be any wallet capable of verifying signatures const verifier = new ProtoWallet_js_1.default('anyone'); const verificationData = this.toBinary(false); // Exclude the signature from the verification data const signatureHex = this.signature ?? ''; // Provide a fallback value (empty string) const { valid } = await verifier.verifySignature({ signature: Utils.toArray(signatureHex, 'hex'), data: verificationData, protocolID: [2, 'certificate signature'], keyID: `${this.type} ${this.serialNumber}`, counterparty: this.certifier // The certifier is the one who signed the certificate }); return valid; } /** * Signs the certificate using the provided certifier wallet. * * @param {Wallet} certifierWallet - The wallet representing the certifier. * @returns {Promise<void>} */ async sign(certifierWallet) { if (this.signature != null && this.signature.length > 0) { // ✅ Explicitly checking for null/undefined throw new Error(`Certificate has already been signed! Signature present: ${this.signature}`); } // Ensure the certifier declared is the one actually signing this.certifier = (await certifierWallet.getPublicKey({ identityKey: true })).publicKey; const preimage = this.toBinary(false); // Exclude the signature when signing const { signature } = await certifierWallet.createSignature({ data: preimage, protocolID: [2, 'certificate signature'], keyID: `${this.type} ${this.serialNumber}` }); this.signature = Utils.toHex(signature); } /** * Helper function which retrieves the protocol ID and key ID for certificate field encryption. * * For master certificate creation, no serial number is provided because entropy is required * from both the client and the certifier. In this case, the `keyID` is simply the `fieldName`. * * For VerifiableCertificates verifier keyring creation, both the serial number and field name are available, * so the `keyID` is formed by concatenating the `serialNumber` and `fieldName`. * * @param fieldName - The name of the field within the certificate to be encrypted. * @param serialNumber - (Optional) The serial number of the certificate. * @returns An object containing: * - `protocolID` (WalletProtocol): The protocol ID for certificate field encryption. * - `keyID` (string): A unique key identifier. It is the `fieldName` if `serialNumber` is undefined, * otherwise it is a combination of `serialNumber` and `fieldName`. */ static getCertificateFieldEncryptionDetails(fieldName, serialNumber) { return { protocolID: [2, 'certificate field encryption'], keyID: serialNumber ? `${serialNumber} ${fieldName}` : fieldName }; } } exports.default = Certificate; //# sourceMappingURL=Certificate.js.map