Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 53 additions & 6 deletions packages/messaging/__tests__/messages/FundingInput.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { Sequence, Tx } from '@node-dlc/bitcoin';
import { StreamReader } from '@node-dlc/bufio';
import { expect } from 'chai';

import { DlcInput } from '../../lib/messages/DlcInput';
import { FundingInput } from '../../lib/messages/FundingInput';

describe('FundingInput', () => {
Expand Down Expand Up @@ -150,11 +147,46 @@ describe('FundingInput', () => {
});

describe('validate', () => {
it('should ensure inputs are segwit', () => {
// Valid segwit transaction with P2WPKH output at index 0
const validSegwitTx =
'020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03520101ffffffff0200f2052a01000000160014f32b43ed1a9f5a7f09023af065a212d8a159c7f70000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000';

it('should throw if output is not segwit (OP_RETURN)', () => {
// Using output index 1 which is an OP_RETURN output (not a segwit output)
const fundingInput = FundingInput.fromJSON({
inputSerialId: 1,
prevTx: validSegwitTx,
prevTxVout: 1, // OP_RETURN output - not valid for funding
sequence: 4294967295,
maxWitnessLen: 108,
redeemScript: '',
});

expect(function () {
fundingInput.validate();
}).to.throw('fundingInput must spend a segwit output');
});

it('should pass validation for P2WPKH output', () => {
const fundingInput = FundingInput.fromJSON({
inputSerialId: 1,
prevTx: validSegwitTx,
prevTxVout: 0, // P2WPKH output
sequence: 4294967295,
maxWitnessLen: 108,
redeemScript: '',
});

expect(function () {
fundingInput.validate();
}).to.not.throw();
});

it('should pass validation for P2WSH output', () => {
const fundingInput = FundingInput.fromJSON({
inputSerialId: 1,
prevTx:
'02000000000100c2eb0b00000000160014e70dcc9ffa7ff84c889c9e79b218708bae3bc95800000000', // has no inputs
'0200000001f58f85b356ad5bb5b6d0ef3eb863be8a6cb95e08e1e9e92885b4b22e7e51eb9d0000000000ffffffff01008c8647000000002200201234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef00000000', // P2WSH output
prevTxVout: 0,
sequence: 4294967295,
maxWitnessLen: 108,
Expand All @@ -163,7 +195,22 @@ describe('FundingInput', () => {

expect(function () {
fundingInput.validate();
}).to.throw(Error);
}).to.not.throw();
});

it('should throw if prevTxVout does not exist', () => {
const fundingInput = FundingInput.fromJSON({
inputSerialId: 1,
prevTx: validSegwitTx,
prevTxVout: 5, // Output index 5 doesn't exist
sequence: 4294967295,
maxWitnessLen: 108,
redeemScript: '',
});

expect(function () {
fundingInput.validate();
}).to.throw('fundingInput prevTxVout 5 does not exist in prevTx');
});
});
});
34 changes: 32 additions & 2 deletions packages/messaging/lib/messages/FundingInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,38 @@ export class FundingInput implements IDlcMessage {
*/
public validate(): void {
// 1. Type is set automatically in class
// 2. Ensure inputs are segwit
if (!this.prevTx.isSegWit) throw new Error('fundingInput must be segwit');
// 2. Ensure the output being spent is a segwit output (P2WPKH or P2WSH)
// Note: We check the output script type, not whether prevTx was created with segwit inputs.
// This allows spending UTXOs from transactions that used legacy inputs but have segwit outputs.
const output = this.prevTx.outputs[this.prevTxVout];
if (!output) {
throw new Error(
`fundingInput prevTxVout ${this.prevTxVout} does not exist in prevTx`,
);
}

const scriptPubKey = output.scriptPubKey.serializeCmds();
// P2WPKH: OP_0 <20-byte-key-hash> = 0x0014{20 bytes} = 22 bytes total
// P2WSH: OP_0 <32-byte-script-hash> = 0x0020{32 bytes} = 34 bytes total
// P2TR: OP_1 <32-byte-pubkey> = 0x5120{32 bytes} = 34 bytes total
const isP2WPKH =
scriptPubKey.length === 22 &&
scriptPubKey[0] === 0x00 &&
scriptPubKey[1] === 0x14;
const isP2WSH =
scriptPubKey.length === 34 &&
scriptPubKey[0] === 0x00 &&
scriptPubKey[1] === 0x20;
const isP2TR =
scriptPubKey.length === 34 &&
scriptPubKey[0] === 0x51 &&
scriptPubKey[1] === 0x20;

if (!isP2WPKH && !isP2WSH && !isP2TR) {
throw new Error(
'fundingInput must spend a segwit output (P2WPKH, P2WSH, or P2TR)',
);
}
}

/**
Expand Down
Loading