Skip to content

Commit c9a1c97

Browse files
committed
fix: handle zero-input transaction parsing
When parsing a transaction with 0 inputs, the byte sequence 00 01 (vinLen=0, voutLen=1) was incorrectly interpreted as segwit marker/flag. This fix validates the segwit interpretation by checking: 1. If vinLen would be 0 (segwit tx with 0 inputs is invalid) 2. If the buffer is too small to contain the claimed number of inputs If either check fails, fall back to non-segwit parsing. Fixes #2293
1 parent e4280e2 commit c9a1c97

File tree

3 files changed

+43
-7
lines changed

3 files changed

+43
-7
lines changed

src/cjs/transaction.cjs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,19 @@ class Transaction {
107107
marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
108108
flag === Transaction.ADVANCED_TRANSACTION_FLAG
109109
) {
110-
hasWitnesses = true;
110+
const { bigintValue: vinLenPeek, bytes: vinLenBytes } =
111+
bufferutils_js_1.varuint.decode(buffer, bufferReader.offset);
112+
const minInputSize = 41;
113+
const remainingAfterVinLen =
114+
buffer.length - bufferReader.offset - vinLenBytes;
115+
if (
116+
vinLenPeek === BigInt(0) ||
117+
Number(vinLenPeek) * minInputSize > remainingAfterVinLen
118+
) {
119+
bufferReader.offset -= 2;
120+
} else {
121+
hasWitnesses = true;
122+
}
111123
} else {
112124
bufferReader.offset -= 2;
113125
}

src/esm/transaction.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,21 @@ export class Transaction {
6565
marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
6666
flag === Transaction.ADVANCED_TRANSACTION_FLAG
6767
) {
68-
hasWitnesses = true;
68+
const { bigintValue: vinLenPeek, bytes: vinLenBytes } = varuint.decode(
69+
buffer,
70+
bufferReader.offset,
71+
);
72+
const minInputSize = 41;
73+
const remainingAfterVinLen =
74+
buffer.length - bufferReader.offset - vinLenBytes;
75+
if (
76+
vinLenPeek === BigInt(0) ||
77+
Number(vinLenPeek) * minInputSize > remainingAfterVinLen
78+
) {
79+
bufferReader.offset -= 2;
80+
} else {
81+
hasWitnesses = true;
82+
}
6983
} else {
7084
bufferReader.offset -= 2;
7185
}

ts_src/transaction.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,33 @@ export class Transaction {
7777

7878
static fromBuffer(buffer: Uint8Array, _NO_STRICT?: boolean): Transaction {
7979
const bufferReader = new BufferReader(buffer);
80-
8180
const tx = new Transaction();
8281
tx.version = bufferReader.readUInt32();
83-
8482
const marker = bufferReader.readUInt8();
8583
const flag = bufferReader.readUInt8();
86-
8784
let hasWitnesses = false;
8885
if (
8986
marker === Transaction.ADVANCED_TRANSACTION_MARKER &&
9087
flag === Transaction.ADVANCED_TRANSACTION_FLAG
9188
) {
92-
hasWitnesses = true;
89+
const { bigintValue: vinLenPeek, bytes: vinLenBytes } = varuint.decode(
90+
buffer,
91+
bufferReader.offset,
92+
);
93+
const minInputSize = 41;
94+
const remainingAfterVinLen =
95+
buffer.length - bufferReader.offset - vinLenBytes;
96+
if (
97+
vinLenPeek === BigInt(0) ||
98+
Number(vinLenPeek) * minInputSize > remainingAfterVinLen
99+
) {
100+
bufferReader.offset -= 2;
101+
} else {
102+
hasWitnesses = true;
103+
}
93104
} else {
94105
bufferReader.offset -= 2;
95106
}
96-
97107
const vinLen = bufferReader.readVarInt();
98108
for (let i = 0; i < vinLen; ++i) {
99109
tx.ins.push({

0 commit comments

Comments
 (0)