@bsv/sdk
Version:
BSV Blockchain Software Development Kit
216 lines • 10.6 kB
JavaScript
"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