@btc-vision/transaction 1.2.15 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,13 +14,13 @@ import { BufferLike, i32, Selector, u16, u32, u8 } from '../utils/types.js';
14
14
 
15
15
  export class BinaryReader {
16
16
  private buffer: DataView;
17
-
18
17
  private currentOffset: i32 = 0;
19
18
 
20
19
  constructor(bytes: BufferLike) {
21
20
  this.buffer = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
22
21
  }
23
22
 
23
+ // Helpers for comparisons; unchanged
24
24
  public static stringCompare(a: string, b: string): number {
25
25
  return a.localeCompare(b);
26
26
  }
@@ -39,246 +39,280 @@ export class BinaryReader {
39
39
 
40
40
  public setBuffer(bytes: BufferLike): void {
41
41
  this.buffer = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
42
-
43
42
  this.currentOffset = 0;
44
43
  }
45
44
 
46
- public readAddressArray(): Address[] {
47
- const length = this.readU16();
48
- const result: Address[] = new Array<Address>(length);
45
+ /**
46
+ * Reads a single unsigned byte (u8).
47
+ */
48
+ public readU8(): u8 {
49
+ this.verifyEnd(this.currentOffset + U8_BYTE_LENGTH);
50
+ const value = this.buffer.getUint8(this.currentOffset);
51
+ this.currentOffset += U8_BYTE_LENGTH;
52
+ return value;
53
+ }
49
54
 
55
+ /**
56
+ * Reads an unsigned 16-bit integer. By default, big-endian.
57
+ * @param be - Endianness; true means big-endian (the default).
58
+ */
59
+ public readU16(be: boolean = true): u16 {
60
+ this.verifyEnd(this.currentOffset + U16_BYTE_LENGTH);
61
+ const value = this.buffer.getUint16(this.currentOffset, !be);
62
+ this.currentOffset += U16_BYTE_LENGTH;
63
+ return value;
64
+ }
65
+
66
+ /**
67
+ * Reads an unsigned 32-bit integer. By default, big-endian.
68
+ * @param be - Endianness; true means big-endian (the default).
69
+ */
70
+ public readU32(be: boolean = true): u32 {
71
+ this.verifyEnd(this.currentOffset + U32_BYTE_LENGTH);
72
+ const value = this.buffer.getUint32(this.currentOffset, !be);
73
+ this.currentOffset += U32_BYTE_LENGTH;
74
+ return value;
75
+ }
76
+
77
+ /**
78
+ * Reads an unsigned 64-bit integer. By default, big-endian.
79
+ * @param be - Endianness; true means big-endian (the default).
80
+ */
81
+ public readU64(be: boolean = true): bigint {
82
+ this.verifyEnd(this.currentOffset + U64_BYTE_LENGTH);
83
+ const value = this.buffer.getBigUint64(this.currentOffset, !be);
84
+ this.currentOffset += U64_BYTE_LENGTH;
85
+ return value;
86
+ }
87
+
88
+ /**
89
+ * Reads a 128-bit unsigned integer. By default, read big-endian.
90
+ * @param be - Endianness; true => big-endian (default).
91
+ */
92
+ public readU128(be: boolean = true): bigint {
93
+ const raw = this.readBytes(U128_BYTE_LENGTH);
94
+ let bytes = raw;
95
+ // If data was written in little-endian, we reverse before interpreting
96
+ if (!be) {
97
+ bytes = this.reverseBytes(raw);
98
+ }
99
+ return BigInt('0x' + this.toHexString(bytes));
100
+ }
101
+
102
+ /**
103
+ * Reads a 256-bit unsigned integer. Same approach as readU128.
104
+ * @param be - Endianness; true => big-endian (default).
105
+ */
106
+ public readU256(be: boolean = true): bigint {
107
+ const raw = this.readBytes(U256_BYTE_LENGTH);
108
+ let bytes = raw;
109
+ if (!be) {
110
+ bytes = this.reverseBytes(raw);
111
+ }
112
+ return BigInt('0x' + this.toHexString(bytes));
113
+ }
114
+
115
+ /**
116
+ * Reads a 128-bit signed integer. Interpret the sign bit if big-endian.
117
+ * @param be - Endianness; true => big-endian (default).
118
+ */
119
+ public readI128(be: boolean = true): bigint {
120
+ const raw = this.readBytes(I128_BYTE_LENGTH);
121
+ let bytes = raw;
122
+ if (!be) {
123
+ bytes = this.reverseBytes(raw);
124
+ }
125
+
126
+ // Construct as a 128-bit two's complement
127
+ let value = BigInt('0x' + this.toHexString(bytes));
128
+
129
+ // If the top bit is set (sign bit in big-endian), interpret negative
130
+ const signBitMask = 0x80;
131
+ if (bytes[0] & signBitMask) {
132
+ // (1 << 128)
133
+ const twoTo128 = BigInt(1) << BigInt(128);
134
+ // 2's complement
135
+ value = value - twoTo128;
136
+ }
137
+ return value;
138
+ }
139
+
140
+ /**
141
+ * Read a boolean (u8 != 0).
142
+ */
143
+ public readBoolean(): boolean {
144
+ return this.readU8() !== 0;
145
+ }
146
+
147
+ /**
148
+ * Reads 32 bits
149
+ */
150
+ public readSelector(): Selector {
151
+ return this.readU32(true);
152
+ }
153
+
154
+ /**
155
+ * Reads a raw sequence of bytes (length must be known).
156
+ * If zeroStop = true, stops if we encounter 0x00 early.
157
+ */
158
+ public readBytes(length: u32, zeroStop: boolean = false): Uint8Array {
159
+ this.verifyEnd(this.currentOffset + length);
160
+ let bytes = new Uint8Array(length);
161
+
162
+ for (let i: u32 = 0; i < length; i++) {
163
+ const b = this.buffer.getUint8(this.currentOffset++);
164
+ if (zeroStop && b === 0) {
165
+ bytes = bytes.subarray(0, i);
166
+ break;
167
+ }
168
+ bytes[i] = b;
169
+ }
170
+ return bytes;
171
+ }
172
+
173
+ /**
174
+ * Reads a string of the given length in raw bytes. By default, do NOT zero-stop
175
+ * (matching how we wrote the raw bytes).
176
+ */
177
+ public readString(length: u16): string {
178
+ const textDecoder = new TextDecoder();
179
+ const bytes = this.readBytes(length, false);
180
+ return textDecoder.decode(bytes);
181
+ }
182
+
183
+ /**
184
+ * Reads a string that was written as [u16 length][raw bytes].
185
+ */
186
+ public readStringWithLength(be: boolean = true): string {
187
+ const length = this.readU16(be);
188
+ return this.readString(length);
189
+ }
190
+
191
+ /**
192
+ * Reads an address.
193
+ */
194
+ public readAddress(): Address {
195
+ const bytes: u8[] = Array.from(this.readBytes(ADDRESS_BYTE_LENGTH));
196
+ return new Address(bytes);
197
+ }
198
+
199
+ /**
200
+ * Reads bytes written as [u32 length][bytes].
201
+ * @param maxLength if > 0, enforces an upper bound
202
+ * @param be
203
+ */
204
+ public readBytesWithLength(maxLength: number = 0, be: boolean = true): Uint8Array {
205
+ const length = this.readU32(be);
206
+ if (maxLength > 0 && length > maxLength) {
207
+ throw new Error('Data length exceeds maximum length.');
208
+ }
209
+
210
+ return this.readBytes(length);
211
+ }
212
+
213
+ // ------------------ Array readers ------------------ //
214
+
215
+ public readAddressArray(be: boolean = true): Address[] {
216
+ const length = this.readU16(be);
217
+ const result: Address[] = new Array<Address>(length);
50
218
  for (let i = 0; i < length; i++) {
51
219
  result[i] = this.readAddress();
52
220
  }
53
-
54
221
  return result;
55
222
  }
56
223
 
57
- public readU256Array(): bigint[] {
58
- const length = this.readU16();
224
+ public readU256Array(be: boolean = true): bigint[] {
225
+ const length = this.readU16(be);
59
226
  const result: bigint[] = new Array<bigint>(length);
60
-
61
227
  for (let i = 0; i < length; i++) {
62
- result[i] = this.readU256();
228
+ result[i] = this.readU256(be);
63
229
  }
64
-
65
230
  return result;
66
231
  }
67
232
 
68
- public readU128Array(): bigint[] {
69
- const length = this.readU16();
233
+ public readU128Array(be: boolean = true): bigint[] {
234
+ const length = this.readU16(be);
70
235
  const result: bigint[] = new Array<bigint>(length);
71
-
72
236
  for (let i = 0; i < length; i++) {
73
- result[i] = this.readU128();
237
+ result[i] = this.readU128(be);
74
238
  }
75
-
76
239
  return result;
77
240
  }
78
241
 
79
- public readU64Array(): bigint[] {
80
- const length = this.readU16();
242
+ public readU64Array(be: boolean = true): bigint[] {
243
+ const length = this.readU16(be);
81
244
  const result: bigint[] = new Array<bigint>(length);
82
-
83
245
  for (let i = 0; i < length; i++) {
84
- result[i] = this.readU64();
246
+ result[i] = this.readU64(be);
85
247
  }
86
-
87
248
  return result;
88
249
  }
89
250
 
90
- public readU32Array(): u32[] {
91
- const length = this.readU16();
251
+ public readU32Array(be: boolean = true): u32[] {
252
+ const length = this.readU16(be);
92
253
  const result: u32[] = new Array<u32>(length);
93
-
94
254
  for (let i = 0; i < length; i++) {
95
- result[i] = this.readU32();
255
+ result[i] = this.readU32(be);
96
256
  }
97
-
98
257
  return result;
99
258
  }
100
259
 
101
- public readU16Array(): u16[] {
102
- const length = this.readU16();
260
+ public readU16Array(be: boolean = true): u16[] {
261
+ const length = this.readU16(be);
103
262
  const result: u16[] = new Array<u16>(length);
104
-
105
263
  for (let i = 0; i < length; i++) {
106
- result[i] = this.readU16();
264
+ result[i] = this.readU16(be);
107
265
  }
108
-
109
266
  return result;
110
267
  }
111
268
 
112
269
  public readU8Array(): u8[] {
113
- const length = this.readU16();
270
+ const length = this.readU16(true); // by default big-endian
114
271
  const result: u8[] = new Array<u8>(length);
115
-
116
272
  for (let i = 0; i < length; i++) {
117
273
  result[i] = this.readU8();
118
274
  }
119
-
120
275
  return result;
121
276
  }
122
277
 
123
- public readStringArray(): string[] {
124
- const length = this.readU16();
278
+ public readStringArray(be: boolean = true): string[] {
279
+ const length = this.readU16(be);
125
280
  const result: string[] = new Array<string>(length);
126
-
127
281
  for (let i = 0; i < length; i++) {
128
- result[i] = this.readStringWithLength();
282
+ result[i] = this.readStringWithLength(be);
129
283
  }
130
-
131
284
  return result;
132
285
  }
133
286
 
134
- public readBytesArray(): Uint8Array[] {
135
- const length = this.readU16();
287
+ public readBytesArray(be: boolean = true): Uint8Array[] {
288
+ const length = this.readU16(be);
136
289
  const result: Uint8Array[] = new Array<Uint8Array>(length);
137
-
138
290
  for (let i = 0; i < length; i++) {
139
- result[i] = this.readBytesWithLength();
291
+ result[i] = this.readBytesWithLength(0, be);
140
292
  }
141
-
142
293
  return result;
143
294
  }
144
295
 
145
- public readBytesWithLength(maxLength: number = 0): Uint8Array {
146
- const length = this.readU32();
147
-
148
- if (maxLength > 0 && length > maxLength) {
149
- throw new Error('Data length exceeds maximum length.');
150
- }
151
-
152
- return this.readBytes(length);
153
- }
154
-
155
- public readU8(): u8 {
156
- this.verifyEnd(this.currentOffset + U8_BYTE_LENGTH);
157
-
158
- const value = this.buffer.getUint8(this.currentOffset);
159
- this.currentOffset += U8_BYTE_LENGTH;
160
-
161
- return value;
162
- }
163
-
164
- public readU16(): u16 {
165
- this.verifyEnd(this.currentOffset + U16_BYTE_LENGTH);
166
-
167
- const value = this.buffer.getUint16(this.currentOffset, true);
168
- this.currentOffset += U16_BYTE_LENGTH;
169
-
170
- return value;
171
- }
172
-
173
- public readU32(le: boolean = true): u32 {
174
- this.verifyEnd(this.currentOffset + U32_BYTE_LENGTH);
175
-
176
- const value = this.buffer.getUint32(this.currentOffset, le);
177
- this.currentOffset += U32_BYTE_LENGTH;
178
-
179
- return value;
180
- }
181
-
182
- public readU64(): bigint {
183
- this.verifyEnd(this.currentOffset + U64_BYTE_LENGTH);
184
-
185
- const value: bigint = this.buffer.getBigUint64(this.currentOffset, true);
186
- this.currentOffset += U64_BYTE_LENGTH;
187
-
188
- return value;
189
- }
190
-
191
- public readAddressValueTuple(): AddressMap<bigint> {
192
- const length = this.readU16();
296
+ /**
297
+ * Reads [u16 length][ (address, u256) pairs ].
298
+ */
299
+ public readAddressValueTuple(be: boolean = true): AddressMap<bigint> {
300
+ const length = this.readU16(be);
193
301
  const result = new AddressMap<bigint>();
194
302
 
195
303
  for (let i = 0; i < length; i++) {
196
304
  const address = this.readAddress();
197
- const value = this.readU256();
198
-
199
- if (result.has(address)) throw new Error('Duplicate address found in map');
305
+ const value = this.readU256(be);
200
306
 
307
+ if (result.has(address)) {
308
+ throw new Error('Duplicate address found in map');
309
+ }
201
310
  result.set(address, value);
202
311
  }
203
-
204
312
  return result;
205
313
  }
206
314
 
207
- public readU128(): bigint {
208
- const next16Bytes = this.readBytes(U128_BYTE_LENGTH);
209
-
210
- return BigInt(
211
- '0x' + next16Bytes.reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), ''),
212
- );
213
- }
214
-
215
- public readU256(): bigint {
216
- const next32Bytes = this.readBytes(U256_BYTE_LENGTH);
217
-
218
- return BigInt(
219
- '0x' + next32Bytes.reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), ''),
220
- );
221
- }
222
-
223
- readI128() {
224
- const next16Bytes = this.readBytes(I128_BYTE_LENGTH);
225
- let value = BigInt(
226
- '0x' + next16Bytes.reduce((acc, byte) => acc + byte.toString(16).padStart(2, '0'), ''),
227
- );
228
-
229
- if (next16Bytes[0] & 0x80) {
230
- const mask = (BigInt(1) << BigInt(128)) - BigInt(1);
231
- value = (value ^ mask) + BigInt(1);
232
- value = -value;
233
- }
234
-
235
- return value;
236
- }
237
-
238
- public readBytes(length: u32, zeroStop: boolean = false): Uint8Array {
239
- let bytes: Uint8Array = new Uint8Array(length);
240
- for (let i: u32 = 0; i < length; i++) {
241
- const byte: u8 = this.readU8();
242
- if (zeroStop && byte === 0) {
243
- bytes = bytes.slice(0, i);
244
- break;
245
- }
246
-
247
- bytes[i] = byte;
248
- }
249
-
250
- return bytes;
251
- }
252
-
253
- public readString(length: u16): string {
254
- const textDecoder = new TextDecoder();
255
- const bytes = this.readBytes(length, true);
256
-
257
- return textDecoder.decode(bytes);
258
- }
259
-
260
- public readSelector(): Selector {
261
- return this.readU32(false);
262
- }
263
-
264
- public readStringWithLength(): string {
265
- const length = this.readU16();
266
-
267
- return this.readString(length);
268
- }
269
-
270
- public readBoolean(): boolean {
271
- return this.readU8() !== 0;
272
- }
273
-
274
- public readAddress(): Address {
275
- const bytes: u8[] = new Array<u8>(ADDRESS_BYTE_LENGTH);
276
- for (let i: u32 = 0; i < ADDRESS_BYTE_LENGTH; i++) {
277
- bytes[i] = this.readU8();
278
- }
279
-
280
- return new Address(bytes);
281
- }
315
+ // --------------------------------------------------- //
282
316
 
283
317
  public getOffset(): u16 {
284
318
  return this.currentOffset;
@@ -288,9 +322,32 @@ export class BinaryReader {
288
322
  this.currentOffset = offset;
289
323
  }
290
324
 
325
+ /**
326
+ * Verifies we have enough bytes in the buffer to read up to `size`.
327
+ */
291
328
  public verifyEnd(size: i32): void {
292
- if (this.currentOffset > this.buffer.byteLength) {
293
- throw new Error(`Expected to read ${size} bytes but read ${this.currentOffset} bytes`);
329
+ if (size > this.buffer.byteLength) {
330
+ throw new Error(
331
+ `Attempt to read beyond buffer length: requested up to byte offset ${size}, but buffer is only ${this.buffer.byteLength} bytes.`,
332
+ );
294
333
  }
295
334
  }
335
+
336
+ /**
337
+ * Utility: reverses a byte array in-place or returns a reversed copy.
338
+ */
339
+ private reverseBytes(bytes: Uint8Array): Uint8Array {
340
+ const out = new Uint8Array(bytes.length);
341
+ for (let i = 0; i < bytes.length; i++) {
342
+ out[i] = bytes[bytes.length - 1 - i];
343
+ }
344
+ return out;
345
+ }
346
+
347
+ /**
348
+ * Utility: turn bytes into a hex string without `0x` prefix.
349
+ */
350
+ private toHexString(bytes: Uint8Array): string {
351
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
352
+ }
296
353
  }