UNPKG

@node-dlc/messaging

Version:
285 lines 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DlcAcceptContainer = exports.DlcAcceptWithoutSigs = exports.DlcAcceptV0 = exports.DlcAccept = void 0; const bitcoin_1 = require("@node-dlc/bitcoin"); const bufio_1 = require("@node-dlc/bufio"); const crypto_1 = require("@node-dlc/crypto"); const bitcoinjs_lib_1 = require("bitcoinjs-lib"); 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 CetAdaptorSignaturesV0_1 = require("./CetAdaptorSignaturesV0"); const FundingInput_1 = require("./FundingInput"); const NegotiationFields_1 = require("./NegotiationFields"); class DlcAccept { static deserialize(buf, parseCets = true) { const reader = new bufio_1.BufferReader(buf); const type = Number(reader.readUInt16BE()); switch (type) { case MessageType_1.MessageType.DlcAcceptV0: return DlcAcceptV0.deserialize(buf, parseCets); default: throw new Error(`Dlc Accept message type must be DlcAcceptV0`); } } } exports.DlcAccept = DlcAccept; /** * DlcAccept contains information about a node and indicates its * acceptance of the new DLC, as well as its CET and refund * transaction signatures. This is the second step toward creating * the funding transaction and closing transactions. */ class DlcAcceptV0 extends DlcAccept { constructor() { super(...arguments); /** * The type for accept_channel message. accept_channel = 33 */ this.type = DlcAcceptV0.type; this.fundingInputs = []; } /** * Deserializes an oracle_info message * @param buf */ static deserialize(buf, parseCets = true) { const instance = new DlcAcceptV0(); const reader = new bufio_1.BufferReader(buf); reader.readUInt16BE(); // read type instance.tempContractId = reader.readBytes(32); instance.acceptCollateralSatoshis = reader.readUInt64BE(); instance.fundingPubKey = reader.readBytes(33); const payoutSPKLen = reader.readUInt16BE(); instance.payoutSPK = reader.readBytes(payoutSPKLen); instance.payoutSerialId = reader.readUInt64BE(); const fundingInputsLen = reader.readUInt16BE(); for (let i = 0; i < fundingInputsLen; i++) { instance.fundingInputs.push(FundingInput_1.FundingInputV0.deserialize((0, getTlv_1.getTlv)(reader))); } const changeSPKLen = reader.readUInt16BE(); instance.changeSPK = reader.readBytes(changeSPKLen); instance.changeSerialId = reader.readUInt64BE(); if (parseCets) { instance.cetSignatures = CetAdaptorSignaturesV0_1.CetAdaptorSignaturesV0.deserialize((0, getTlv_1.getTlv)(reader)); } else { (0, getTlv_1.skipTlv)(reader); instance.cetSignatures = new CetAdaptorSignaturesV0_1.CetAdaptorSignaturesV0(); } instance.refundSignature = reader.readBytes(64); instance.negotiationFields = NegotiationFields_1.NegotiationFields.deserialize((0, getTlv_1.getTlv)(reader)); 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: break; } } return instance; } /** * Get funding, change and payout address from DlcOffer * @param network Bitcoin Network * @returns {IDlcOfferV0Addresses} */ getAddresses(network) { const fundingSPK = bitcoin_1.Script.p2wpkhLock((0, crypto_1.hash160)(this.fundingPubKey)) .serialize() .slice(1); const fundingAddress = bitcoinjs_lib_1.address.fromOutputScript(fundingSPK, network); const changeAddress = bitcoinjs_lib_1.address.fromOutputScript(this.changeSPK, network); const payoutAddress = bitcoinjs_lib_1.address.fromOutputScript(this.payoutSPK, network); return { fundingAddress, changeAddress, payoutAddress, }; } /** * Validates correctness of all fields * https://github.com/discreetlogcontracts/dlcspecs/blob/master/Protocol.md#the-accept_dlc-message * @throws Will throw an error if validation fails */ validate() { // 1. Type is set automatically in class // 2. payout_spk and change_spk must be standard script pubkeys try { bitcoinjs_lib_1.address.fromOutputScript(this.payoutSPK); } catch (e) { throw new Error('payoutSPK is invalid'); } try { bitcoinjs_lib_1.address.fromOutputScript(this.changeSPK); } catch (e) { throw new Error('changeSPK is invalid'); } // 3. funding_pubkey must be a valid secp256k1 pubkey in compressed format // https://github.com/bitcoin/bips/blob/master/bip-0137.mediawiki#background-on-ecdsa-signatures if (secp256k1_1.default.publicKeyVerify(Buffer.from(this.fundingPubKey))) { if (this.fundingPubKey[0] != 0x02 && this.fundingPubKey[0] != 0x03) { throw new Error('fundingPubKey must be in compressed format'); } } else { throw new Error('fundingPubKey is not a valid secp256k1 key'); } // 4. inputSerialId must be unique for each input const inputSerialIds = this.fundingInputs.map((input) => input.inputSerialId); if (new Set(inputSerialIds).size !== inputSerialIds.length) { throw new Error('inputSerialIds must be unique'); } // 5. Ensure funding inputs are segwit this.fundingInputs.forEach((input) => input.validate()); // validate funding amount const fundingAmount = this.fundingInputs.reduce((acc, fundingInput) => { const input = fundingInput; return acc + input.prevTx.outputs[input.prevTxVout].value.sats; }, BigInt(0)); if (this.acceptCollateralSatoshis >= fundingAmount) { throw new Error('fundingAmount must be greater than acceptCollateralSatoshis'); } } /** * Converts dlc_accept_v0 to JSON */ toJSON() { const tlvs = []; if (this.batchFundingGroups) { this.batchFundingGroups.forEach((group) => { tlvs.push(group.serialize()); }); } return { type: this.type, tempContractId: this.tempContractId.toString('hex'), acceptCollateralSatoshis: Number(this.acceptCollateralSatoshis), fundingPubKey: this.fundingPubKey.toString('hex'), payoutSPK: this.payoutSPK.toString('hex'), payoutSerialId: Number(this.payoutSerialId), fundingInputs: this.fundingInputs.map((input) => input.toJSON()), changeSPK: this.changeSPK.toString('hex'), changeSerialId: Number(this.changeSerialId), cetSignatures: this.cetSignatures.toJSON(), refundSignature: this.refundSignature.toString('hex'), negotiationFields: this.negotiationFields.toJSON(), tlvs, }; } /** * Serializes the accept_channel message into a Buffer */ serialize() { const writer = new bufio_1.BufferWriter(); writer.writeUInt16BE(this.type); writer.writeBytes(this.tempContractId); writer.writeUInt64BE(this.acceptCollateralSatoshis); writer.writeBytes(this.fundingPubKey); writer.writeUInt16BE(this.payoutSPK.length); writer.writeBytes(this.payoutSPK); writer.writeUInt64BE(this.payoutSerialId); writer.writeUInt16BE(this.fundingInputs.length); for (const fundingInput of this.fundingInputs) { writer.writeBytes(fundingInput.serialize()); } writer.writeUInt16BE(this.changeSPK.length); writer.writeBytes(this.changeSPK); writer.writeUInt64BE(this.changeSerialId); writer.writeBytes(this.cetSignatures.serialize()); writer.writeBytes(this.refundSignature); writer.writeBytes(this.negotiationFields.serialize()); if (this.batchFundingGroups) this.batchFundingGroups.forEach((fundingInfo) => writer.writeBytes(fundingInfo.serialize())); return writer.toBuffer(); } withoutSigs() { return new DlcAcceptWithoutSigs(this.tempContractId, this.acceptCollateralSatoshis, this.fundingPubKey, this.payoutSPK, this.payoutSerialId, this.fundingInputs, this.changeSPK, this.changeSerialId, this.negotiationFields, this.batchFundingGroups); } } exports.DlcAcceptV0 = DlcAcceptV0; DlcAcceptV0.type = MessageType_1.MessageType.DlcAcceptV0; class DlcAcceptWithoutSigs { constructor(tempContractId, acceptCollateralSatoshis, fundingPubKey, payoutSPK, payoutSerialId, fundingInputs, changeSPK, changeSerialId, negotiationFields, batchFundingGroups) { this.tempContractId = tempContractId; this.acceptCollateralSatoshis = acceptCollateralSatoshis; this.fundingPubKey = fundingPubKey; this.payoutSPK = payoutSPK; this.payoutSerialId = payoutSerialId; this.fundingInputs = fundingInputs; this.changeSPK = changeSPK; this.changeSerialId = changeSerialId; this.negotiationFields = negotiationFields; this.batchFundingGroups = batchFundingGroups; } } exports.DlcAcceptWithoutSigs = DlcAcceptWithoutSigs; class DlcAcceptContainer { constructor() { this.accepts = []; } /** * Adds a DlcAccept to the container. * @param accept The DlcAccept to add. */ addAccept(accept) { this.accepts.push(accept); } /** * Returns all DlcAccepts in the container. * @returns An array of DlcAccept instances. */ getAccepts() { return this.accepts; } /** * Serializes all DlcAccepts in the container to a Buffer. * @returns A Buffer containing the serialized DlcAccepts. */ serialize() { const writer = new bufio_1.BufferWriter(); // Write the number of accepts in the container first. writer.writeBigSize(this.accepts.length); // Serialize each accept and write it. this.accepts.forEach((accept) => { const serializedAccept = accept.serialize(); // Optionally, write the length of the serialized accept for easier deserialization. writer.writeBigSize(serializedAccept.length); writer.writeBytes(serializedAccept); }); return writer.toBuffer(); } /** * Deserializes a Buffer into a DlcAcceptContainer with DlcAccepts. * @param buf The Buffer to deserialize. * @returns A DlcAcceptContainer instance. */ static deserialize(buf, parseCets = true) { const reader = new bufio_1.BufferReader(buf); const container = new DlcAcceptContainer(); const acceptsCount = reader.readBigSize(); for (let i = 0; i < acceptsCount; i++) { const acceptLength = reader.readBigSize(); const acceptBuf = reader.readBytes(Number(acceptLength)); const accept = DlcAccept.deserialize(acceptBuf, parseCets); // Adjust based on actual implementation. container.addAccept(accept); } return container; } } exports.DlcAcceptContainer = DlcAcceptContainer; //# sourceMappingURL=DlcAccept.js.map