UNPKG

@node-dlc/messaging

Version:
275 lines 12.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DlcSignContainer = exports.DlcSign = void 0; const bufio_1 = require("@node-dlc/bufio"); const secp256k1_1 = __importDefault(require("secp256k1")); const MessageType_1 = require("../MessageType"); const deserializeTlv_1 = require("../serialize/deserializeTlv"); const getTlv_1 = require("../serialize/getTlv"); const BatchFundingGroup_1 = require("./BatchFundingGroup"); const CetAdaptorSignatures_1 = require("./CetAdaptorSignatures"); const FundingSignatures_1 = require("./FundingSignatures"); const ScriptWitnessV0_1 = require("./ScriptWitnessV0"); /** * DlcSign gives all of the initiator's signatures, which allows the * receiver to broadcast the funding transaction with both parties being * fully committed to all closing transactions. * Updated to support dlcspecs PR #163 format. */ class DlcSign { constructor() { /** * The type for sign_dlc message. sign_dlc = 42782 */ this.type = DlcSign.type; // New fields as per dlcspecs PR #163 this.protocolVersion = MessageType_1.PROTOCOL_VERSION; // Default to current protocol version } /** * Creates a DlcSign from JSON data (e.g., from test vectors) * Handles both our internal format and external test vector formats * @param json JSON object representing a DLC sign */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any static fromJSON(json) { const instance = new DlcSign(); // Helper function to parse DER-encoded signature and extract raw r,s values (64 bytes total) const parseDerSignature = (hexSig) => { const sigBuffer = Buffer.from(hexSig, 'hex'); // If it's already 64 bytes, assume it's raw if (sigBuffer.length === 64) { return sigBuffer; } // Use secp256k1.signatureImport to parse DER signature try { const rawSig = secp256k1_1.default.signatureImport(sigBuffer); return Buffer.from(rawSig); } catch (ex) { throw new Error(`Invalid DER signature: ${ex.message}`); } }; // Handle both internal and external field naming conventions instance.protocolVersion = json.protocolVersion || json.protocol_version || MessageType_1.PROTOCOL_VERSION; instance.contractId = Buffer.from(json.contractId || json.contract_id, 'hex'); // Parse CET adaptor signatures instance.cetAdaptorSignatures = DlcSign.parseCetAdaptorSignaturesFromJSON(json.cetAdaptorSignatures || json.cet_adaptor_signatures); // Parse refund signature - handle DER encoding const refundSigHex = json.refundSignature || json.refund_signature; instance.refundSignature = parseDerSignature(refundSigHex); // Parse funding signatures instance.fundingSignatures = DlcSign.parseFundingSignaturesFromJSON(json.fundingSignatures || json.funding_signatures); return instance; } /** * Parses CetAdaptorSignatures from JSON * @param cetSigsJson JSON object representing CET adaptor signatures */ static parseCetAdaptorSignaturesFromJSON( // eslint-disable-next-line @typescript-eslint/no-explicit-any cetSigsJson) { const instance = new CetAdaptorSignatures_1.CetAdaptorSignatures(); if (cetSigsJson.ecdsaAdaptorSignatures || cetSigsJson.ecdsa_adaptor_signatures) { const ecdsaSigs = cetSigsJson.ecdsaAdaptorSignatures || cetSigsJson.ecdsa_adaptor_signatures; // eslint-disable-next-line @typescript-eslint/no-explicit-any instance.sigs = ecdsaSigs.map((sig) => { // The test vectors use 'signature' field, but our internal format uses encryptedSig/dleqProof // For now, we'll parse the signature as encryptedSig and leave dleqProof empty const sigBuffer = Buffer.from(sig.signature, 'hex'); return { encryptedSig: sigBuffer.slice(0, 65), dleqProof: sigBuffer.length > 65 ? sigBuffer.slice(65, 162) : Buffer.alloc(97), // Next 97 bytes or empty }; }); } return instance; } /** * Parses FundingSignatures from JSON * @param fundingSigsJson JSON object representing funding signatures */ static parseFundingSignaturesFromJSON( // eslint-disable-next-line @typescript-eslint/no-explicit-any fundingSigsJson) { const instance = new FundingSignatures_1.FundingSignatures(); if (fundingSigsJson.fundingSignatures || fundingSigsJson.funding_signatures) { const fundingSigs = fundingSigsJson.fundingSignatures || fundingSigsJson.funding_signatures; // eslint-disable-next-line @typescript-eslint/no-explicit-any instance.witnessElements = fundingSigs.map((sig) => (sig.witnessElements || sig.witness_elements || []).map( // eslint-disable-next-line @typescript-eslint/no-explicit-any (element) => { // Create a ScriptWitnessV0 instance for each witness element const witness = new ScriptWitnessV0_1.ScriptWitnessV0(); witness.witness = Buffer.from(element.witness || element, 'hex'); return witness; })); } return instance; } /** * Deserializes a sign_dlc message * @param buf */ static deserialize(buf) { const instance = new DlcSign(); const reader = new bufio_1.BufferReader(buf); reader.readUInt16BE(); // read type // New fields as per dlcspecs PR #163 instance.protocolVersion = reader.readUInt32BE(); instance.contractId = reader.readBytes(32); // Read CET adaptor signatures directly to match serialize format (no TLV wrapping) instance.cetAdaptorSignatures = CetAdaptorSignatures_1.CetAdaptorSignatures.deserialize(reader.buffer.subarray(reader.position)); // Skip past the CET adaptor signatures we just read const cetLength = instance.cetAdaptorSignatures.serialize().length; reader.position += cetLength; instance.refundSignature = reader.readBytes(64); // Read funding signatures directly to match serialize format (no TLV wrapping) instance.fundingSignatures = FundingSignatures_1.FundingSignatures.deserialize(reader.buffer.subarray(reader.position)); // Skip past the funding signatures we just read const fundingLength = instance.fundingSignatures.serialize().length; reader.position += fundingLength; // Parse TLV stream as per dlcspecs PR #163 while (!reader.eof) { const buf = (0, getTlv_1.getTlv)(reader); const tlvReader = new bufio_1.BufferReader(buf); const { type } = (0, deserializeTlv_1.deserializeTlv)(tlvReader); switch (Number(type)) { case MessageType_1.MessageType.BatchFundingGroup: if (!instance.batchFundingGroups) { instance.batchFundingGroups = []; } instance.batchFundingGroups.push(BatchFundingGroup_1.BatchFundingGroup.deserialize(buf)); break; default: // Store unknown TLVs for future compatibility if (!instance.unknownTlvs) { instance.unknownTlvs = []; } instance.unknownTlvs.push({ type: Number(type), data: buf }); break; } } return instance; } /** * Validates correctness of all fields * Updated validation rules as per dlcspecs PR #163 * @throws Will throw an error if validation fails */ validate() { // 1. Type is set automatically in class // 2. protocol_version validation if (this.protocolVersion !== MessageType_1.PROTOCOL_VERSION) { throw new Error(`Unsupported protocol version: ${this.protocolVersion}, expected: ${MessageType_1.PROTOCOL_VERSION}`); } // 3. contract_id must be 32 bytes if (!this.contractId || this.contractId.length !== 32) { throw new Error('contractId must be 32 bytes'); } // 4. Other validations would depend on specific business logic // TODO: Add more specific validation rules as needed } /** * Converts sign_dlc to JSON (canonical rust-dlc format) */ toJSON() { // Convert raw signature back to DER format for canonical rust-dlc JSON const derRefundSignature = secp256k1_1.default.signatureExport(this.refundSignature); return { protocolVersion: this.protocolVersion, contractId: this.contractId.toString('hex'), cetAdaptorSignatures: this.cetAdaptorSignatures.toJSON(), refundSignature: Buffer.from(derRefundSignature).toString('hex'), fundingSignatures: this.fundingSignatures.toJSON(), }; } /** * Serializes the sign_dlc message into a Buffer * Updated serialization format as per dlcspecs PR #163 */ serialize() { const writer = new bufio_1.BufferWriter(); writer.writeUInt16BE(this.type); // New fields as per dlcspecs PR #163 writer.writeUInt32BE(this.protocolVersion); writer.writeBytes(this.contractId); writer.writeBytes(this.cetAdaptorSignatures.serialize()); writer.writeBytes(this.refundSignature); writer.writeBytes(this.fundingSignatures.serialize()); // TLV stream as per dlcspecs PR #163 if (this.batchFundingGroups) this.batchFundingGroups.forEach((fundingInfo) => writer.writeBytes(fundingInfo.serialize())); // Write unknown TLVs for forward compatibility if (this.unknownTlvs) { this.unknownTlvs.forEach((tlv) => { writer.writeBytes(tlv.data); }); } return writer.toBuffer(); } } exports.DlcSign = DlcSign; DlcSign.type = MessageType_1.MessageType.DlcSign; class DlcSignContainer { constructor() { this.signs = []; } /** * Adds a DlcSign to the container. * @param sign The DlcSign to add. */ addSign(sign) { this.signs.push(sign); } /** * Returns all DlcSigns in the container. * @returns An array of DlcSign instances. */ getSigns() { return this.signs; } /** * Serializes all DlcSigns in the container to a Buffer. * @returns A Buffer containing the serialized DlcSigns. */ serialize() { const writer = new bufio_1.BufferWriter(); // Write the number of signs in the container first. writer.writeBigSize(this.signs.length); // Serialize each sign and write it. this.signs.forEach((sign) => { const serializedSign = sign.serialize(); // Optionally, write the length of the serialized sign for easier deserialization. writer.writeBigSize(serializedSign.length); writer.writeBytes(serializedSign); }); return writer.toBuffer(); } /** * Deserializes a Buffer into a DlcSignContainer with DlcSigns. * @param buf The Buffer to deserialize. * @returns A DlcSignContainer instance. */ static deserialize(buf) { const reader = new bufio_1.BufferReader(buf); const container = new DlcSignContainer(); const signsCount = reader.readBigSize(); for (let i = 0; i < signsCount; i++) { // Optionally, read the length of the serialized sign if it was written during serialization. const signLength = reader.readBigSize(); const signBuf = reader.readBytes(Number(signLength)); const sign = DlcSign.deserialize(signBuf); container.addSign(sign); } return container; } } exports.DlcSignContainer = DlcSignContainer; //# sourceMappingURL=DlcSign.js.map