From dcc04f2736195648506cd90a9d830bc1364168b8 Mon Sep 17 00:00:00 2001 From: Dillion Verma Date: Tue, 10 Aug 2021 10:26:40 -0400 Subject: [PATCH 1/6] Dlc close messagetype --- .../__tests__/messages/DlcCloseV0.spec.ts | 41 +++++++++ packages/messaging/lib/MessageType.ts | 2 + packages/messaging/lib/messages/DlcClose.ts | 86 +++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 packages/messaging/__tests__/messages/DlcCloseV0.spec.ts create mode 100644 packages/messaging/lib/messages/DlcClose.ts diff --git a/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts b/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts new file mode 100644 index 00000000..537adb01 --- /dev/null +++ b/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts @@ -0,0 +1,41 @@ +import { expect } from 'chai'; +import { DlcCloseV0 } from '../../lib/messages/DlcClose'; + +describe('DlcCloseV0', () => { + const contractId = Buffer.from( + 'c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269', + 'hex', + ); + + describe('serialize', () => { + it('serializes', () => { + const instance = new DlcCloseV0(); + + instance.contractId = contractId; + + // instance.cancelType = 0; + + expect(instance.serialize().toString("hex")).to.equal( + "cbcc" + + "c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269" + // "00" // cancel type + ); // prettier-ignore + }); + }); + + describe('deserialize', () => { + it('deserializes', () => { + const buf = Buffer.from( + "cbcc" + + "c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269" + // "00" // cancel type + , "hex" + ); // prettier-ignore + + const instance = DlcCloseV0.deserialize(buf); + + expect(instance.contractId).to.deep.equal(contractId); + // expect(instance.cancelType).to.equal(0); + }); + }); +}); diff --git a/packages/messaging/lib/MessageType.ts b/packages/messaging/lib/MessageType.ts index a6e25507..b3a26322 100644 --- a/packages/messaging/lib/MessageType.ts +++ b/packages/messaging/lib/MessageType.ts @@ -48,6 +48,8 @@ export enum MessageType { DlcCancelV0 = 52172, + DlcCloseV0 = 11111, // TODO + /** * Dlc Storage Types */ diff --git a/packages/messaging/lib/messages/DlcClose.ts b/packages/messaging/lib/messages/DlcClose.ts new file mode 100644 index 00000000..cbc79cd9 --- /dev/null +++ b/packages/messaging/lib/messages/DlcClose.ts @@ -0,0 +1,86 @@ +import { BufferReader, BufferWriter } from '@node-lightning/bufio'; +import { MessageType } from '../MessageType'; +import { getTlv } from '../serialize/getTlv'; +import { IDlcMessage } from './DlcMessage'; +import { FundingInputV0 } from './FundingInput'; + +export abstract class DlcClose { + public static deserialize(buf: Buffer): DlcCloseV0 { + const reader = new BufferReader(buf); + + const type = Number(reader.readUInt16BE()); + + switch (type) { + case MessageType.DlcCloseV0: + return DlcCloseV0.deserialize(buf); + default: + throw new Error(`DLC Close message type must be DlcCloseV0`); // This is a temporary measure while protocol is being developed + } + } + + public abstract type: number; + + public abstract serialize(): Buffer; +} + +/** + * DlcOffer message contains information about a node and indicates its + * desire to enter into a new contract. This is the first step toward + * creating the funding transaction and CETs. + */ +export class DlcCloseV0 extends DlcClose implements IDlcMessage { + public static type = MessageType.DlcCloseV0; + + /** + * Deserializes an offer_dlc_v0 message + * @param buf + */ + public static deserialize(buf: Buffer): DlcCloseV0 { + const instance = new DlcCloseV0(); + const reader = new BufferReader(buf); + + reader.readUInt16BE(); // read type + instance.contractId = reader.readBytes(32); + instance.closeSignature = reader.readBytes(64); + instance.offerPayoutSatoshis = reader.readUInt64BE(); + instance.acceptPayoutSatoshis = reader.readUInt64BE(); + const fundingInputsLen = reader.readUInt16BE(); + for (let i = 0; i < fundingInputsLen; i++) { + instance.fundingInputs.push(FundingInputV0.deserialize(getTlv(reader))); + } + + return instance; + } + + /** + * The type for close_dlc_v0 message. close_dlc_v0 = 99999 // TODO + */ + public type = DlcCloseV0.type; + + public contractId: Buffer; + + public closeSignature: Buffer; + + public offerPayoutSatoshis: bigint; + + public acceptPayoutSatoshis: bigint; + + public fundingInputs: FundingInputV0[] = []; + + /** + * Serializes the close_dlc_v0 message into a Buffer + */ + public serialize(): Buffer { + const writer = new BufferWriter(); + writer.writeUInt16BE(this.type); + writer.writeBytes(this.contractId); + writer.writeBytes(this.closeSignature); + writer.writeUInt64BE(this.offerPayoutSatoshis); + writer.writeUInt64BE(this.acceptPayoutSatoshis); + for (const fundingInput of this.fundingInputs) { + writer.writeBytes(fundingInput.serialize()); + } + + return writer.toBuffer(); + } +} From ecc33de252c4fb11a3075134af247c34fdf6a52f Mon Sep 17 00:00:00 2001 From: Dillion Verma Date: Tue, 10 Aug 2021 10:40:29 -0400 Subject: [PATCH 2/6] update test info --- .../__tests__/messages/DlcCloseV0.spec.ts | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts b/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts index 537adb01..11de9e00 100644 --- a/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts +++ b/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts @@ -1,5 +1,6 @@ import { expect } from 'chai'; import { DlcCloseV0 } from '../../lib/messages/DlcClose'; +import { FundingInputV0 } from '../../lib/messages/FundingInput'; describe('DlcCloseV0', () => { const contractId = Buffer.from( @@ -7,18 +8,34 @@ describe('DlcCloseV0', () => { 'hex', ); + const fundingInputsLen = Buffer.from('0001', 'hex'); + const fundingInputV0 = Buffer.from( + 'fda714' + // type funding_input_v0 + '3f' + // length + '000000000000dae8' + // input_serial_id + '0029' + // prevtx_len + '02000000000100c2eb0b000000001600149ea3bf2d6eb9c2ffa35e36f41e117403ed7fafe900000000' + // prevtx + '00000000' + // prevtx_vout + 'ffffffff' + // sequence + '006b' + // max_witness_len + '0000', // redeem_script_len + 'hex', + ); + describe('serialize', () => { it('serializes', () => { const instance = new DlcCloseV0(); instance.contractId = contractId; - - // instance.cancelType = 0; + // instance.closeSignature = closeSignature; + // instance.offerPayoutSatoshis = offerPayoutSatoshis; + // instance.acceptPayoutSatoshis = acceptPayoutSatoshis; + instance.fundingInputs = [FundingInputV0.deserialize(fundingInputV0)]; expect(instance.serialize().toString("hex")).to.equal( "cbcc" + "c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269" - // "00" // cancel type + // "00" ); // prettier-ignore }); }); @@ -35,7 +52,18 @@ describe('DlcCloseV0', () => { const instance = DlcCloseV0.deserialize(buf); expect(instance.contractId).to.deep.equal(contractId); - // expect(instance.cancelType).to.equal(0); + // expect(instance.closeSignature).to.deep.equal(closeSignature); + // expect(instance.offerPayoutSatoshis).to.deep.equal(offerPayoutSatoshis); + // expect(instance.acceptPayoutSatoshis).to.deep.equal(acceptPayoutSatoshis); + expect(instance.fundingInputs[0].serialize().toString('hex')).to.equal( + fundingInputV0.toString('hex'), + ); + }); + + it('has correct type', () => { + // expect(DlcCloseV0.deserialize(dlcCloseHex).type).to.equal( + // MessageType.DlcCloseV0, + // ); }); }); }); From ac257ce2dcadde805858148fc4f46055f2e0569b Mon Sep 17 00:00:00 2001 From: Dillion Verma Date: Tue, 10 Aug 2021 10:58:43 -0400 Subject: [PATCH 3/6] stub more fields in tests --- .../__tests__/messages/DlcCloseV0.spec.ts | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts b/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts index 11de9e00..b78f539b 100644 --- a/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts +++ b/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts @@ -1,13 +1,26 @@ import { expect } from 'chai'; import { DlcCloseV0 } from '../../lib/messages/DlcClose'; import { FundingInputV0 } from '../../lib/messages/FundingInput'; +import { MessageType } from '../../lib/MessageType'; describe('DlcCloseV0', () => { + let instance: DlcCloseV0; + + const type = Buffer.from('2B67', 'hex'); + const contractId = Buffer.from( 'c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269', 'hex', ); + const closeSignature = Buffer.from( + '7c8ad6de287b62a1ed1d74ed9116a5158abc7f97376d201caa88e0f9daad68fcda4c271cc003512e768f403a57e5242bd1f6aa1750d7f3597598094a43b1c7bb', + 'hex', + ); + + const offerPayoutSatoshis = Buffer.from('0000000005f5e100', 'hex'); + const acceptPayoutSatoshis = Buffer.from('0000000005f5e100', 'hex'); + const fundingInputsLen = Buffer.from('0001', 'hex'); const fundingInputV0 = Buffer.from( 'fda714' + // type funding_input_v0 @@ -22,6 +35,16 @@ describe('DlcCloseV0', () => { 'hex', ); + const dlcCloseHex = Buffer.concat([ + type, + contractId, + closeSignature, + offerPayoutSatoshis, + acceptPayoutSatoshis, + fundingInputsLen, + fundingInputV0, + ]); + describe('serialize', () => { it('serializes', () => { const instance = new DlcCloseV0(); @@ -61,9 +84,9 @@ describe('DlcCloseV0', () => { }); it('has correct type', () => { - // expect(DlcCloseV0.deserialize(dlcCloseHex).type).to.equal( - // MessageType.DlcCloseV0, - // ); + expect(DlcCloseV0.deserialize(dlcCloseHex).type).to.equal( + MessageType.DlcCloseV0, + ); }); }); }); From 49b0c3be3049b75da730942bdfc186fced90b31f Mon Sep 17 00:00:00 2001 From: Dillion Verma Date: Wed, 11 Aug 2021 10:47:27 -0400 Subject: [PATCH 4/6] add validations and update tests --- .../__tests__/messages/DlcCloseV0.spec.ts | 114 ++++++++++++------ packages/messaging/lib/MessageType.ts | 3 +- packages/messaging/lib/messages/DlcClose.ts | 54 ++++++++- 3 files changed, 129 insertions(+), 42 deletions(-) diff --git a/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts b/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts index b78f539b..34456975 100644 --- a/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts +++ b/packages/messaging/__tests__/messages/DlcCloseV0.spec.ts @@ -1,12 +1,13 @@ import { expect } from 'chai'; -import { DlcCloseV0 } from '../../lib/messages/DlcClose'; + +import { DlcClose, DlcCloseV0 } from '../../lib/messages/DlcClose'; import { FundingInputV0 } from '../../lib/messages/FundingInput'; import { MessageType } from '../../lib/MessageType'; -describe('DlcCloseV0', () => { +describe('DlcClose', () => { let instance: DlcCloseV0; - const type = Buffer.from('2B67', 'hex'); + const type = Buffer.from('cbca', 'hex'); const contractId = Buffer.from( 'c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269', @@ -45,48 +46,85 @@ describe('DlcCloseV0', () => { fundingInputV0, ]); - describe('serialize', () => { - it('serializes', () => { - const instance = new DlcCloseV0(); - - instance.contractId = contractId; - // instance.closeSignature = closeSignature; - // instance.offerPayoutSatoshis = offerPayoutSatoshis; - // instance.acceptPayoutSatoshis = acceptPayoutSatoshis; - instance.fundingInputs = [FundingInputV0.deserialize(fundingInputV0)]; - - expect(instance.serialize().toString("hex")).to.equal( - "cbcc" + - "c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269" - // "00" - ); // prettier-ignore - }); + beforeEach(() => { + instance = new DlcCloseV0(); + instance.contractId = contractId; + instance.closeSignature = closeSignature; + instance.offerPayoutSatoshis = BigInt(100000000); + instance.acceptPayoutSatoshis = BigInt(100000000); + instance.fundingInputs = [FundingInputV0.deserialize(fundingInputV0)]; }); describe('deserialize', () => { - it('deserializes', () => { - const buf = Buffer.from( - "cbcc" + - "c1c79e1e9e2fa2840b2514902ea244f39eb3001a4037a52ea43c797d4f841269" - // "00" // cancel type - , "hex" - ); // prettier-ignore - - const instance = DlcCloseV0.deserialize(buf); - - expect(instance.contractId).to.deep.equal(contractId); - // expect(instance.closeSignature).to.deep.equal(closeSignature); - // expect(instance.offerPayoutSatoshis).to.deep.equal(offerPayoutSatoshis); - // expect(instance.acceptPayoutSatoshis).to.deep.equal(acceptPayoutSatoshis); - expect(instance.fundingInputs[0].serialize().toString('hex')).to.equal( - fundingInputV0.toString('hex'), - ); + it('should throw if incorrect type', () => { + instance.type = 0x123; + expect(function () { + DlcClose.deserialize(instance.serialize()); + }).to.throw(Error); }); it('has correct type', () => { - expect(DlcCloseV0.deserialize(dlcCloseHex).type).to.equal( - MessageType.DlcCloseV0, + expect(DlcClose.deserialize(instance.serialize()).type).to.equal( + instance.type, ); }); }); + + describe('DlcCloseV0', () => { + describe('serialize', () => { + it('serializes', () => { + expect(instance.serialize().toString('hex')).to.equal( + dlcCloseHex.toString('hex'), + ); + }); + }); + + describe('deserialize', () => { + it('deserializes', () => { + const instance = DlcCloseV0.deserialize(dlcCloseHex); + expect(instance.contractId).to.deep.equal(contractId); + expect(instance.closeSignature).to.deep.equal(closeSignature); + expect(Number(instance.offerPayoutSatoshis)).to.equal(100000000); + expect(Number(instance.acceptPayoutSatoshis)).to.equal(100000000); + expect(instance.fundingInputs[0].serialize().toString('hex')).to.equal( + fundingInputV0.toString('hex'), + ); + }); + + it('has correct type', () => { + expect(DlcCloseV0.deserialize(dlcCloseHex).type).to.equal( + MessageType.DlcCloseV0, + ); + }); + }); + + describe('toJSON', () => { + it('convert to JSON', async () => { + const json = instance.toJSON(); + expect(json.contractId).to.equal(contractId.toString('hex')); + expect(json.closeSignature).to.equal(closeSignature.toString('hex')); + expect(json.fundingInputs[0].prevTx).to.equal( + instance.fundingInputs[0].prevTx.serialize().toString('hex'), + ); + }); + }); + + describe('validate', () => { + it('should throw if inputSerialIds arent unique', () => { + instance.fundingInputs = [ + FundingInputV0.deserialize(fundingInputV0), + FundingInputV0.deserialize(fundingInputV0), + ]; + expect(function () { + instance.validate(); + }).to.throw(Error); + }); + it('should ensure funding inputs are segwit', () => { + instance.fundingInputs = [FundingInputV0.deserialize(fundingInputV0)]; + expect(function () { + instance.validate(); + }).to.throw(Error); + }); + }); + }); }); diff --git a/packages/messaging/lib/MessageType.ts b/packages/messaging/lib/MessageType.ts index b3a26322..d5d82e36 100644 --- a/packages/messaging/lib/MessageType.ts +++ b/packages/messaging/lib/MessageType.ts @@ -46,10 +46,9 @@ export enum MessageType { DlcAcceptV0 = 42780, DlcSignV0 = 42782, + DlcCloseV0 = 52170, // TODO: Temporary type DlcCancelV0 = 52172, - DlcCloseV0 = 11111, // TODO - /** * Dlc Storage Types */ diff --git a/packages/messaging/lib/messages/DlcClose.ts b/packages/messaging/lib/messages/DlcClose.ts index cbc79cd9..01e2275a 100644 --- a/packages/messaging/lib/messages/DlcClose.ts +++ b/packages/messaging/lib/messages/DlcClose.ts @@ -1,13 +1,16 @@ import { BufferReader, BufferWriter } from '@node-lightning/bufio'; + import { MessageType } from '../MessageType'; import { getTlv } from '../serialize/getTlv'; import { IDlcMessage } from './DlcMessage'; -import { FundingInputV0 } from './FundingInput'; +import { FundingInputV0, IFundingInputV0JSON } from './FundingInput'; export abstract class DlcClose { public static deserialize(buf: Buffer): DlcCloseV0 { const reader = new BufferReader(buf); + // console.log('OK', Number(reader.readUInt16BE())); + const type = Number(reader.readUInt16BE()); switch (type) { @@ -20,6 +23,8 @@ export abstract class DlcClose { public abstract type: number; + public abstract toJSON(): IDlcCloseV0JSON; + public abstract serialize(): Buffer; } @@ -53,7 +58,7 @@ export class DlcCloseV0 extends DlcClose implements IDlcMessage { } /** - * The type for close_dlc_v0 message. close_dlc_v0 = 99999 // TODO + * The type for close_dlc_v0 message. close_dlc_v0 = 52170 // TODO */ public type = DlcCloseV0.type; @@ -77,10 +82,55 @@ export class DlcCloseV0 extends DlcClose implements IDlcMessage { writer.writeBytes(this.closeSignature); writer.writeUInt64BE(this.offerPayoutSatoshis); writer.writeUInt64BE(this.acceptPayoutSatoshis); + writer.writeUInt16BE(this.fundingInputs.length); + for (const fundingInput of this.fundingInputs) { writer.writeBytes(fundingInput.serialize()); } return writer.toBuffer(); } + + /** + * Validates correctness of all fields + * @throws Will throw an error if validation fails + */ + public validate(): void { + // Type is set automatically in class + + // Ensure input serial ids are unique + const inputSerialIds = this.fundingInputs.map( + (input: FundingInputV0) => input.inputSerialId, + ); + + if (new Set(inputSerialIds).size !== inputSerialIds.length) { + throw new Error('inputSerialIds must be unique'); + } + + // Ensure funding inputs are segwit + this.fundingInputs.forEach((input: FundingInputV0) => input.validate()); + } + + /** + * Converts dlc_close_v0 to JSON + */ + public toJSON(): IDlcCloseV0JSON { + return { + type: this.type, + contractId: this.contractId.toString('hex'), + closeSignature: this.closeSignature.toString('hex'), + offerPayoutSatoshis: Number(this.offerPayoutSatoshis), + acceptPayoutSatoshis: Number(this.acceptPayoutSatoshis), + fundingInputs: this.fundingInputs.map((input) => input.toJSON()), + }; + } +} + +export interface IDlcCloseV0JSON { + type: number; + contractId: string; + closeSignature: string; + offerPayoutSatoshis: number; + acceptPayoutSatoshis: number; + fundingInputs: IFundingInputV0JSON[]; } From 69b8e3bb8a3f2c51e2f556d68cedcc485f38f714 Mon Sep 17 00:00:00 2001 From: Dillion Verma Date: Wed, 11 Aug 2021 11:39:43 -0400 Subject: [PATCH 5/6] update comment --- packages/messaging/lib/messages/DlcClose.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/messaging/lib/messages/DlcClose.ts b/packages/messaging/lib/messages/DlcClose.ts index 01e2275a..ec10aded 100644 --- a/packages/messaging/lib/messages/DlcClose.ts +++ b/packages/messaging/lib/messages/DlcClose.ts @@ -29,15 +29,14 @@ export abstract class DlcClose { } /** - * DlcOffer message contains information about a node and indicates its - * desire to enter into a new contract. This is the first step toward - * creating the funding transaction and CETs. + * DlcClose message contains information about a node and indicates its + * desire to close an existing contract. */ export class DlcCloseV0 extends DlcClose implements IDlcMessage { public static type = MessageType.DlcCloseV0; /** - * Deserializes an offer_dlc_v0 message + * Deserializes an close_dlc_v0 message * @param buf */ public static deserialize(buf: Buffer): DlcCloseV0 { From ba4b2f179431d7c2ea37d09ed1146d7290217b40 Mon Sep 17 00:00:00 2001 From: Dillion Verma Date: Wed, 11 Aug 2021 11:40:38 -0400 Subject: [PATCH 6/6] remove random comment --- packages/messaging/lib/messages/DlcClose.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/messaging/lib/messages/DlcClose.ts b/packages/messaging/lib/messages/DlcClose.ts index ec10aded..26c3ccfa 100644 --- a/packages/messaging/lib/messages/DlcClose.ts +++ b/packages/messaging/lib/messages/DlcClose.ts @@ -9,8 +9,6 @@ export abstract class DlcClose { public static deserialize(buf: Buffer): DlcCloseV0 { const reader = new BufferReader(buf); - // console.log('OK', Number(reader.readUInt16BE())); - const type = Number(reader.readUInt16BE()); switch (type) {